Outline

This notebook fits various configurations of the memory model to the full set of retrieval practice data. We vary the following factors:

  • Subset: fit all data, fit by learner, fit by amount of practice
  • Temporal scope: fit all intervals, only short intervals (0-10 min), only intervals around 24 h, different numbers of time bins
  • Parameter: fit retrieval threshold \(\tau\), decay \(d\), scaling factor \(h\)

Finally, the fitted models are compared in terms of goodness of fit. Since the fits use different numbers of parameters and slightly different subsets of the full dataset, the comparison uses average Akaike Information Criterion (AIC) scores.

Setup

library(data.table)
library(purrr)

Attaching package: 'purrr'
The following object is masked from 'package:data.table':

    transpose
The following object is masked from 'package:base':

    %||%
library(furrr)
Loading required package: future
library(here)
here() starts at /Users/maarten/Documents/projects/mfadts
library(ggplot2)
library(patchwork)
library(ggtext)

source("00_helper_funs.R")
Loading required package: tidyr
pred_col <- "#CC3311"    # red
obs_col <- "#000000"     # black
window_col <- "#33BBEE"  # blue
section_col <- "#FD520F" # orange

future::plan("multisession", workers = 8) # Set to desired number of cores

set.seed(0)

Model fitting setup

Set default parameters for the ACT-R memory model. These values are used whenever a parameter is not fitted.

model_params <- list(
  tau = -3.0, # Retrieval threshold
  s = .5, # Activation noise
  decay = .5, # Decay
  h = 1 # Scaling factor
)

Data setup

d_f <- fread(file.path("..", "data", "cogpsych_data_formatted.csv"))

Each user-fact pair has a single learning sequence associated with it, consisting of three or more trials in one session and the first trial in the next session.

Isolate the last observation per learning sequence (i.e., the one after the between-session interval). This is the observation that the model has to predict, given all prior observations in the sequence.

d_last <- d_f[, .SD[.N], by = id]

Create subsets

Split the data by learner, so that we can fit each learner’s data separately.

d_f_by_learner <- split(d_f, by = "user")
d_last_by_learner <- split(d_last, by = "user")

Split the data by the amount of practice (i.e., the number of trials within a sequence), so that we can fit different amounts of practice separately. Each sequence consists of at least 4 trials. To keep things manageable, we group sequences with over 21 trials into a single “21+” bucket.

trials_by_id <- d_f[, .(trials = .N), by = .(id)]
trials_by_id[, trials := factor(ifelse(trials < 21, trials, "21+"),
                                levels = c(4:20, "21+"))]
d_f <- d_f[trials_by_id, on = "id"]
d_last <- d_last[trials_by_id, on = "id"]

d_f_by_practice <- split(d_f, by = "trials")
d_last_by_practice <- split(d_last, by = "trials")

Define time bins

Split the data by the between-session interval. This interval ranges from seconds to weeks. We vary the number of splits (windows) between 1 (all data in a single window) and 20. Window ranges are equally sized on a logarithmic scale; the middle of each window is the geometric mean of its boundaries.

n_windows <- c(1, 5, 10, 20)

window_range <- map(n_windows, function (n_w) {

  d_windows <- copy(d_last)
  
  if (n_w == 1) {
    d_windows[, window := 1]
  } else {
    d_windows[, window := cut(log(time_between), breaks = n_w, labels = FALSE)]
  }  
  
  # Get the window range(s)
  window_range <- d_windows[, .(start = min(time_between), end = max(time_between)), by = .(window)]
  window_range[, geom_mean := sqrt(start*end), by = .(window)]
  setorder(window_range, window)
  window_range[, window := window]
  window_range[, n_windows := n_w]

  return (window_range)
}) |>
  rbindlist()

window_range[, window_type := "regular"]

We’ll also include a “short” window (0-10 min) and a “24h” window (23.5-24.5h), to see how well the model performs if fitted only to these intervals.

window_range <- rbind(window_range,
                      list(window = 1,
                           start = window_range[, min(start)],
                           end = 10*60,
                           geom_mean = sqrt((window_range[, min(start)]) * (10*60)),
                           n_windows = 1, 
                           window_type = "short"),
                      list(window = 1,
                           start = 23.5*60*60,
                           end = 24.5*60*60,
                           geom_mean = sqrt(23.5*24.5)*60*60,
                           n_windows = 1,
                           window_type = "24h"))

window_range[, window_id := 1:.N]

The resulting window ranges:

window_range

The distribution of between-session intervals looks as follows:

p_histogram <- ggplot() +
  # Window background
  geom_rect(data = window_range[1], aes(xmin = start/60, xmax = end/60, ymin = -Inf, ymax = Inf), fill = window_col, alpha = .1) +
  # Histogram
  geom_histogram(data = d_last, aes(x = time_between/60, y = ..ncount..), bins = 100, fill = obs_col) +
  # Plot setup
  scale_x_log10(
    breaks = scales::trans_breaks("log10", function(x) 10^x),
    labels = scales::trans_format("log10", scales::math_format(10^.x)),
    expand = c(0, 0),
    sec.axis = sec_axis(~.x, breaks = label_x, labels = label_txt)
  ) +
  scale_y_continuous(breaks = seq(0, 1, by = .25)) +
  labs(x = "Between-session interval (minutes)",
       y = "Density") +
  annotation_logticks(sides = "b", outside = T) +
  coord_cartesian(ylim = c(0, 1), xlim = c(window_range[1, start], window_range[1, end])/60, clip = "off") +
  theme_bw(base_size = 14) +
  theme(plot.margin = margin(7, 14, 7, 7),
        panel.grid.major.x = element_blank(),
        panel.grid.minor = element_blank(),
        panel.border = element_blank(),
        axis.text.x = element_text(margin = margin(t = 8)),
        axis.text.x.top = element_text(margin = margin(b = 8)))

p_histogram
Warning: The dot-dot notation (`..ncount..`) was deprecated in ggplot2 3.4.0.
ℹ Please use `after_stat(ncount)` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

Fit models

Define the model fitting function:

fit_model <- function (subset = c("all", "by_learner", "by_practice")) {

  subset <- match.arg(subset)
  message("Subset: ", subset)

  d_fit <- switch(subset,
                  "all" = list(copy(d_last)),
                  "by_learner" = copy(d_last_by_learner),
                  "by_practice" = copy(d_last_by_practice))
  
  # Iterate over subsets
  fit_out <- map(d_fit, function (d_fit_sub) {
    
    # Iterate over window ranges
    windows <- copy(window_range)
    
    # Only fit the "short" and "24h" windows to all sequences
    if (subset != "all") {
      windows <- windows[window_type == "regular"]
    }
    
    fit_windows <- split(windows, by = "window_id")
    fit_params <- future_map(fit_windows, function (fit_window) {
      
      # Only include sequences within the window bounds: <start, end]
      # But: if this is the first window, do include the lower bound
      if (fit_window$window == 1) {
        d_fit_sub_window <- d_fit_sub[time_between >= fit_window$start & time_between <= fit_window$end]
      } else {
        d_fit_sub_window <- d_fit_sub[time_between > fit_window$start & time_between <= fit_window$end]
      }
      
      # To identify parameters, require at least 3 responses in the window, with a mix of correct and incorrect responses
      if (!(nrow(d_fit_sub_window) >= 3 && between(mean(d_fit_sub_window$correct), 0, 1, incbounds = FALSE))) {
        return (NULL)
      }
      
      # Prepare data
      d_fit_sub_window[, window := fit_window$window]
      d_fit_sub_window[, sequence := 1:.N]
      d_fit_sub_window <- d_f[d_fit_sub_window[, .(id, window, sequence)], on = .(id)]
      d_fit_sub_window_seqs <- generate_seq_list(d_fit_sub_window)
      
      # Fit parameters
      d_fit_sub_window_params <- fit_parameters(d_fit_sub_window_seqs, model_params)
      
      # Add subgroup info and window ID
      sub_label <- switch(subset,
                          "all" = NA,
                          "by_learner" = d_fit_sub[1, user],
                          "by_practice" = d_fit_sub[1, trials])
      
      d_fit_sub_window_params <- cbind(fit_window[, .(window_id)], sub_label, d_fit_sub_window_params)
      
      return (d_fit_sub_window_params)
    })
    
    return (fit_params)
  }, .progress = interactive())
  
  # Only keep window fits with observations
  fit_out <- discard(flatten(fit_out), is.null)
  
  # Convert list to data.table
  fit_out <- rbindlist(fit_out)
  
  # Add window information
  fit_out <- fit_out[window_range, on = .(window_id)][!is.na(id)]

  # Add subset information
  fit_out <- cbind(data.table(subset = subset), fit_out)
  
  return (fit_out)
  
}

Fit all variants of the model (note: this can take a while!). (Set use_saved_fit to TRUE to try to load previous fits from file.)

use_saved_fit <- TRUE

fit_all_path <- here("data", "fit_all.csv")
fit_by_learner_path <- here("data", "fit_by_learner.csv")
fit_by_practice_path <- here("data", "fit_by_practice.csv")

if (!use_saved_fit | !file.exists(fit_all_path)) {
  fit_all <- fit_model(subset = "all")
  fwrite(fit_all, fit_all_path)
} else {
  fit_all <- fread(fit_all_path)
}

if (!use_saved_fit | !file.exists(fit_by_learner_path)) {
  fit_by_learner <- fit_model(subset = "by_learner")
  fwrite(fit_by_learner, fit_by_learner_path)
} else {
  fit_by_learner <- fread(fit_by_learner_path)
}

if (!use_saved_fit | !file.exists(fit_by_practice_path)) {
  fit_by_practice <- fit_model(subset = "by_practice")
  fwrite(fit_by_practice, fit_by_practice_path)
} else {
  fit_by_practice <- fread(fit_by_practice_path)
}

fits <- rbind(fit_all, fit_by_learner, fit_by_practice)

Parametric function

In addition to window-specific fits, we can also include a linear model fit based on the window-wise parameter estimates. We only do this for the 20-window split.

fit_by_window <- fits[n_windows == 20, .(tau = tau[1], d = median(d), h = median(h)), by = .(subset, sub_label, n_windows, window)]
fit_by_window <- fit_by_window[window_range[n_windows == 20, .(n_windows, window, geom_mean)], on = .(n_windows, window)]

# Fit a linear model for each set of parameter estimates
fit_lm <- function (geom_mean, param, log_param = FALSE) {
  if (log_param) {
    param <- log(param)
  }
  m <- lm(param ~ log(geom_mean))
  
  return (m)
}

lm_fit <- fit_by_window[, .(lm_tau = list(fit_lm(geom_mean, tau)),
                                   lm_d = list(fit_lm(geom_mean, d)),
                                   lm_h = list(fit_lm(geom_mean, h, log_param = TRUE))), 
                               by = .(subset, sub_label)]

Fitted parameters

Regular fit

Here we fit all learners and amounts of practice together. Red points show the median fitted parameter value in each time bin.

fit_all_avg <- fit_all[,  .(tau = median(tau), d = median(d), h = median(h)),  by = .(n_windows, window_type, window, geom_mean)]

p_tau_all <- ggplot(fit_all, aes(x = geom_mean/60, y = tau)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(data = fit_all_avg, method = "lm", se = FALSE, formula = y ~ x) +
  geom_point(data = fit_all_avg, colour = "red") +
  plot_timescales() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Retrieval threshold tau")

ggsave(here("output", "tau_fit_all.png"), p_tau_all, width = 10, height = 6)

p_d_all <- ggplot(fit_all, aes(x = geom_mean/60, y = d)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(data = fit_all_avg, method = "lm", se = FALSE, formula = y ~ x) +
  geom_point(data = fit_all_avg, colour = "red") +
  plot_timescales() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Decay d")

ggsave(here("output", "d_fit_all.png"), p_d_all, width = 10, height = 6)

p_h_all <- ggplot(fit_all, aes(x = geom_mean/60, y = h)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(data = fit_all_avg, method = "lm", se = FALSE, formula = y ~ x) +
  geom_point(data = fit_all_avg, colour = "red") +
  plot_timescales() +
  scale_y_log10() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Scaling factor h")

ggsave(here("output", "h_fit_all.png"), p_h_all, width = 10, height = 6)

p_h_filt_all <- ggplot(fit_all[h >= 1e-15], aes(x = geom_mean/60, y = h)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(data = fit_all_avg, method = "lm", se = FALSE, formula = y ~ x) +
  geom_point(data = fit_all_avg, colour = "red") +
  plot_timescales() +
  scale_y_log10() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Scaling factor h (h >= 1e-15)")

ggsave(here("output", "h_fit_all_filtered.png"), p_h_filt_all, width = 10, height = 6)

Create a pretty version of the 20-bin parameter plots.

plot_parameter <- function(d_parameter,
                           parameter_name = "",
                           n_w = 1,
                           log_x = TRUE,
                           log_y = FALSE,
                           print_plot = TRUE) {
  
  # Calculate R-squared
  x <- d_parameter[n_windows == n_w, geom_mean]/60
  y <- d_parameter[n_windows == n_w, parameter]
  if (log_x) x <- log(x)
  if (log_y) y <- log(y)
  
  m <- lm(y ~ x)

  eq <- substitute(parameter_name == a-b %*% ln(italic(t)), 
                   list(parameter_name = parameter_name,
                        a = format(unname(coef(m)[1]), digits = 3),
                        b = format(abs(unname(coef(m)[2])), digits = 3)))
  
  eq <- as.character(as.expression(eq))
  
  rsq <- paste("R^2 ==", scales::number(summary(m)$r.squared, accuracy = .01))

  p <- ggplot() +
    # Window background
    geom_rect(data = window_range[n_windows == n_w],
              aes(xmin = start/60, xmax = ifelse(is.na(shift(start, -1)), end, shift(start, -1))/60,
                  ymin = ifelse(log_y, 0, -Inf), ymax = Inf, alpha = as.factor(window)),
              fill = window_col) +
    # Regression line
    geom_smooth(data = d_parameter[n_windows == n_w], 
                aes(y = parameter, x = geom_mean/60), 
                method = "lm", formula = y ~ x, 
                colour = pred_col, fill = pred_col) +
    # Parameter values
    geom_point(data = d_parameter[n_windows == n_w],
               aes(y = parameter, x = geom_mean/60)) +
    scale_alpha_manual(values = rep(c(.1, .25), ceiling(n_w/2))) +
    # R-squared
    geom_label(aes(x = Inf, y = Inf, label = rsq),
              label.padding = unit(.5, "lines"),
              label.size = NA,
              fill = NA,
              hjust = "inward", vjust = "inward",
              parse = TRUE) +
    geom_label(aes(x = ifelse(log_x, 0, -Inf), y = ifelse(log_y, 0, -Inf), label = eq),
              label.padding = unit(.5, "lines"),
              label.size = NA,
              fill = NA,
              hjust = "inward", vjust = "inward",
              parse = TRUE) +
    # Plot setup
    guides(alpha = "none") +
    labs(x = "Between-session interval (minutes)",
         y = "Fitted parameter") +
    scale_x_continuous(sec.axis = sec_axis(~.x, breaks = label_x, labels = label_txt)) +
    coord_cartesian(xlim = c(window_range[1, start], window_range[1, end])/60,
                    ylim = c(min(y) - .1*diff(range(y)), max(y) + .1*diff(range(y))),
                    clip = "off") +
    theme_bw(base_size = 14) +
    theme(plot.margin = margin(7, 14, 7, 7),
          panel.grid.major.x = element_blank(),
          panel.grid.minor = element_blank(),
          panel.border = element_blank(),
          axis.text.x.top = element_text(margin = margin(b = 8)))
  
  
  # Transform scales if required
  if (log_x) {
    p <- p +
      scale_x_log10(
        breaks = scales::trans_breaks("log10", function(x) 10^x),
        labels = scales::trans_format("log10", scales::math_format(10^.x)),
        expand = c(0, 0),
        sec.axis = sec_axis(~.x, breaks = label_x, labels = label_txt)
      ) +
      annotation_logticks(sides = "b", outside = T) +
      theme(axis.text.x = element_text(margin = margin(t = 8)))
  }
  
  if (log_y) {
    p <- p +
      scale_y_log10() +
      annotation_logticks(sides = "l", outside = T) +
      coord_cartesian(xlim = c(window_range[1, start], window_range[1, end])/60,
                      ylim = c(1e-3, 1.25),
                      clip = "off") +
      theme(axis.text.y = element_text(margin = margin(r = 8)))
  }

  if (print_plot) print(p)
  return (p)

}
fit_tau_avg <- copy(fit_all_avg)
setnames(fit_tau_avg, "tau", "parameter")

p_tau_time <- plot_parameter(d_parameter = fit_tau_avg[window_type == "regular"],
                             parameter_name = quote(tau),
                             n_w = 20,
                             print_plot = FALSE)
Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
fit_d_avg <- copy(fit_all_avg)
setnames(fit_d_avg, "d", "parameter")

p_d_time <- plot_parameter(d_parameter = fit_d_avg[window_type == "regular"],
                           parameter_name = quote(italic(d)),
                           n_w = 20,
                           print_plot = FALSE)
Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
fit_h_avg <- copy(fit_all_avg)
setnames(fit_h_avg, "h", "parameter")

p_h_time <- plot_parameter(d_parameter = fit_h_avg[window_type == "regular"],
                           parameter_name = quote(ln(italic(h))),
                           log_y = TRUE,
                           n_w = 20,
                           print_plot = FALSE)
Scale for x is already present.
Adding another scale for x, which will replace the existing scale.
Coordinate system already present. Adding new coordinate system, which will replace the existing one.

Fit by learner

The following plot show the parameters that were fitted per learner (specifically: the lines are linear models fitted to the binwise parameter estimates shown as points). It is clear that nearly all learners exhibit the pattern that is also found at the group-level: as the between-session interval increases, tau/d/h decreases. In addition, there seem to be individual differences in both intercept and slope, which could indicate that a learner-specific fit could produce better results than a group-level fit.

fit_by_learner_avg <- fit_by_learner[,  .(.N, tau = mean(tau), d = mean(d), h = mean(h)),  by = .(n_windows, window_type, window, geom_mean, sub_label)]

p_tau_learner <- ggplot(fit_by_learner_avg, aes(x = geom_mean/60, y = tau, colour = sub_label)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(method = "lm", se = FALSE, formula = y ~ x, linewidth = .1) +
  plot_timescales() +
  scale_y_continuous(limits = c(-7, 0)) +
  guides(colour = "none") +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Retrieval threshold tau by learner")

ggsave(here("output", "tau_fit_by_learner.png"), p_tau_learner, width = 10, height = 6)
Warning: Removed 3 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 3 rows containing missing values or values outside the scale
range (`geom_point()`).

p_d_learner <- ggplot(fit_by_learner_avg, aes(x = geom_mean/60, y = d, colour = sub_label)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(method = "lm", se = FALSE, formula = y ~ x, linewidth = .1) +
  plot_timescales() +
  guides(colour = "none") +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Decay d by learner")

ggsave(here("output", "d_fit_by_learner.png"), p_d_learner, width = 10, height = 6)

p_h_learner <- ggplot(fit_by_learner_avg, aes(x = geom_mean/60, y = h, colour = sub_label)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(method = "lm", se = FALSE, formula = y ~ x, linewidth = .1) +
  plot_timescales() +
  scale_y_log10() +
  guides(colour = "none") +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", title = "Scaling factor h by learner")

ggsave(here("output", "h_fit_by_learner.png"), p_h_learner, width = 10, height = 6)

The plots below show the coefficients from the fitted linear models per learner. Here we also see that most learners are clustered around similar values.

tau_by_learner_lm <- lm_fit[subset == "by_learner", .(map_dfr(lm_tau, function (m) {
  list(intercept = coef(m)[[1]],
       slope = coef(m)[[2]])
}))]

p_tau_learner_lm <- ggplot(tau_by_learner_lm, aes(x = intercept, y = slope)) +
  geom_point(alpha = .25) +
  labs(x = "Intercept", y = "Slope", title = "Retrieval threshold tau by learner")

d_by_learner_lm <- lm_fit[subset == "by_learner", .(map_dfr(lm_d, function (m) {
  list(intercept = coef(m)[[1]],
       slope = coef(m)[[2]])
}))]

p_d_learner_lm <- ggplot(d_by_learner_lm, aes(x = intercept, y = slope)) +
  geom_point(alpha = .25) +
  labs(x = "Intercept", y = "Slope", title = "Decay d by learner")


h_by_learner_lm <- lm_fit[subset == "by_learner", .(map_dfr(lm_h, function (m) {
  list(intercept = coef(m)[[1]],
       slope = coef(m)[[2]])
}))]

p_h_learner_lm <- ggplot(h_by_learner_lm, aes(x = intercept, y = slope)) +
  geom_point(alpha = .25) +
  labs(x = "Intercept", y = "Slope", title = "Scaling factor h by learner")

ggsave(here("output", "tau_fit_by_learner_lm.png"), p_tau_learner_lm, width = 5, height = 4)
Warning: Removed 27 rows containing missing values or values outside the scale
range (`geom_point()`).
ggsave(here("output", "d_fit_by_learner_lm.png"), p_d_learner_lm, width = 5, height = 4)
Warning: Removed 27 rows containing missing values or values outside the scale
range (`geom_point()`).
ggsave(here("output", "h_fit_by_learner_lm.png"), p_h_learner_lm, width = 5, height = 4)
Warning: Removed 27 rows containing missing values or values outside the scale
range (`geom_point()`).

Fit by amount of practice

The plots below show the fitted parameters by amount of practice.

Notice that there seems to be a systematic effect of the amount of practice on the fitted model parameters: more practice in the first session corresponds to a higher retrieval threshold / (initial) decay / scaling factor. A logical explanation for this could be that the amount of practice is correlated with item difficulty: for instance more difficult items are selected more frequently by the adaptive learning system, and the extra practice of these items leads to higher predicted activation in session 2 (because of additional traces), but their difficulty means that accuracy is not actually higher, which means that a stronger decay is necessary to bridge the gap.

fit_by_practice_avg <- fit_by_practice[,  .(.N, tau = mean(tau), d = mean(d), h = mean(h)),  by = .(n_windows, window_type, window, geom_mean, sub_label)]

p_tau_practice <- ggplot(fit_by_practice_avg, aes(x = geom_mean/60, y = tau, colour = sub_label)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(method = "lm", se = FALSE, formula = y ~ x, linewidth = .1) +
  plot_timescales() +
  scale_y_continuous(limits = c(-7, 0)) +
  scale_colour_viridis_d() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", colour = "Trials", title = "Retrieval threshold tau by practice")

ggsave(here("output", "tau_fit_by_practice.png"), p_tau_practice, width = 10, height = 6)

p_d_practice <- ggplot(fit_by_practice_avg, aes(x = geom_mean/60, y = d, colour = sub_label)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(method = "lm", se = FALSE, formula = y ~ x, linewidth = .1) +
  plot_timescales() +
  scale_colour_viridis_d() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", colour = "Trials", title = "Decay d by practice")

ggsave(here("output", "d_fit_by_practice.png"), p_d_practice, width = 10, height = 6)

p_h_practice <- ggplot(fit_by_practice_avg, aes(x = geom_mean/60, y = h, colour = sub_label)) +
  facet_wrap(~ paste0(n_windows, " window(s) (", window_type, ")")) +
  geom_point(alpha = .01) +
  geom_smooth(method = "lm", se = FALSE, formula = y ~ x, linewidth = .1) +
  plot_timescales() +
  scale_y_log10() +
  scale_colour_viridis_d() +
  labs(x = "Between-session interval (min)", y = "Fitted parameter", colour = "Trials", title = "Scaling factor h by practice")

ggsave(here("output", "h_fit_by_practice.png"), p_h_practice, width = 10, height = 6)

The plots below show the coefficients from the fitted linear models per amount of practice.

tau_by_practice_lm <- lm_fit[subset == "by_practice", .(map_dfr(lm_tau, function (m) {
  list(intercept = coef(m)[[1]],
       slope = coef(m)[[2]])
}))]

p_tau_practice_lm <- ggplot(tau_by_practice_lm, aes(x = intercept, y = slope)) +
  geom_point(alpha = .25) +
  labs(x = "Intercept", y = "Slope", title = "Retrieval threshold tau by practice")

d_by_practice_lm <- lm_fit[subset == "by_practice", .(map_dfr(lm_d, function (m) {
  list(intercept = coef(m)[[1]],
       slope = coef(m)[[2]])
}))]

p_d_practice_lm <- ggplot(d_by_practice_lm, aes(x = intercept, y = slope)) +
  geom_point(alpha = .25) +
  labs(x = "Intercept", y = "Slope", title = "Decay d by practice")


h_by_practice_lm <- lm_fit[subset == "by_practice", .(map_dfr(lm_h, function (m) {
  list(intercept = coef(m)[[1]],
       slope = coef(m)[[2]])
}))]

p_h_practice_lm <- ggplot(h_by_practice_lm, aes(x = intercept, y = slope)) +
  geom_point(alpha = .25) +
  labs(x = "Intercept", y = "Slope", title = "Scaling factor h by practice")


ggsave(here("output", "tau_fit_by_practice_lm.png"), p_tau_practice_lm, width = 5, height = 4)
ggsave(here("output", "d_fit_by_practice_lm.png"), p_d_practice_lm, width = 5, height = 4)
ggsave(here("output", "h_fit_by_practice_lm.png"), p_h_practice_lm, width = 5, height = 4)

Evaluate models

Predict recall

Define the function to predict recall from fitted parameters:

predict_recall <- function (model_fit) {
  
  d_fit <- switch(model_fit[1, subset],
                  "all" = list(copy(d_last)),
                  "by_learner" = copy(d_last_by_learner),
                  "by_practice" = copy(d_last_by_practice))
  
  fit_by_window <- split(model_fit, by = "window_id")
  
  future_map(fit_by_window, function (fit_window) {
    
    extrapolate_outside_fitted_window <- fit_window[1, subset == "all" && window_type %in% c("short", "24h")]
    
    if (extrapolate_outside_fitted_window) {
      # In these cases, apply the parameters outside the fitted window
      d_window <- d_fit[[1]]
      d_window[, window := 1]
      d_window[, sequence := 1:.N]
      d_window <- d_f[d_window[, .(id, sequence, window)], on = "id"]
      
    } else {
      # Otherwise, use fitted parameters only in their own window
      fit_window[, sequence := 1:.N]
      d_window <- d_f[fit_window[, .(id, sequence, window)], on = "id"]
    }
    
    d_window_seqs <- generate_seq_list(d_window)
    
    correct <- map_int(d_window_seqs, ~.$correct)
    time_between <- map_dbl(d_window_seqs, ~.$time_between)
    
    if (extrapolate_outside_fitted_window) {
        fitted_tau <- fit_window[1, tau]
        fitted_d <- fit_window[, median(d)]
        fitted_h <- fit_window[, median(h)]
    } else {
        fitted_tau <- fit_window$tau
        fitted_d <- fit_window$d
        fitted_h <- fit_window$h
    }
    
    # Prediction from fitted tau
    ac <- map_dbl(d_window_seqs, function (x) {
      activation(x$time_within, x$time_between, model_params$h, model_params$decay)
    })
    p_recall_tau <- p_recall(ac, fitted_tau, model_params$s)
    
    # Prediction from fitted d
    ac_d <- map2_dbl(d_window_seqs, fitted_d, function (x, d) {
      activation(x$time_within, x$time_between, model_params$h, d)
    })
    p_recall_d <- p_recall(ac_d, model_params$tau, model_params$s)
    
    # Prediction from fitted h
    ac_h <- map2_dbl(d_window_seqs, fitted_h, function (x, h) {
      activation(x$time_within, x$time_between, h, model_params$decay)
    })
    p_recall_h <- p_recall(ac_h, model_params$tau, model_params$s)
    
    fit_info <- fit_window[, .(subset, sub_label, window_id, n_windows, window, geom_mean, window_type, id)]
    
    if (extrapolate_outside_fitted_window) {
      fit_info[, id := NULL]
      fit_info <- cbind(fit_info[1], data.table(id = map_chr(d_window_seqs, ~.$id)))
    }
    
    data.table(fit_info,
               time_between = time_between,
               correct = correct,
               p_recall_tau = p_recall_tau,
               p_recall_d = p_recall_d,
               p_recall_h = p_recall_h)
  }) |>
    rbindlist()
}

Predict recall for all fitted models (note: this can take a while!). (Set use_saved_predictions to TRUE to try to load previous predictions from file.)

use_saved_predictions <- TRUE

pred_all_path <- here("data", "pred_all.csv")
pred_by_learner_path <- here("data", "pred_by_learner.csv")
pred_by_practice_path <- here("data", "pred_by_practice.csv")

if (!use_saved_predictions | !file.exists(pred_all_path)) {
  pred_all <- predict_recall(fit_all)
  fwrite(pred_all, pred_all_path)
} else {
  pred_all <- fread(pred_all_path)
}

if (!use_saved_predictions | !file.exists(pred_by_learner_path)) {
  pred_by_learner <- predict_recall(fit_by_learner)
  fwrite(pred_by_learner, pred_by_learner_path)
} else {
  pred_by_learner <- fread(pred_by_learner_path)
}

if (!use_saved_predictions | !file.exists(pred_by_practice_path)) {
  pred_by_practice <- predict_recall(fit_by_practice)
  fwrite(pred_by_practice, pred_by_practice_path)
} else {
  pred_by_practice <- fread(pred_by_practice_path)
}

preds <- rbind(pred_all, pred_by_learner, pred_by_practice)

Also predict recall using the parametric function:

# Predict recall using the linear model
predict_lm <- function (subset, sub_label, lm_tau, lm_d, lm_h) {
  
  d_pred <- d_last[, .(id, user, trials, time_between, correct)]
  if (subset == "by_learner") {
    d_pred <- d_pred[user == sub_label]
  } else if (subset == "by_practice") {
    d_pred <- d_pred[trials == sub_label]
  }
  
  # Get model parameters per sequence
  tau_fit <- predict(lm_tau, newdata = d_pred[, .(geom_mean = time_between)])
  d_fit <- predict(lm_d, newdata = d_pred[, .(geom_mean = time_between)])
  h_fit <- predict(lm_h, newdata = d_pred[, .(geom_mean = time_between)]) |> exp()
  
  params_fit <- cbind(d_pred[, .(id)], tau_fit, d_fit, h_fit)
  
  # Prepare sequences
  d_pred[, sequence := 1:.N]
  d_pred[, window := 0]
  d_pred_full <- d_f[d_pred[, .(id, sequence, window)], on = .(id)]
  d_pred_seqs <- generate_seq_list(d_pred_full)
  
  # tau
  ac_tau <- map_dbl(d_pred_seqs, function (x) {
    activation(x$time_within, x$time_between, model_params$h, model_params$decay)
  })
  p_recall_tau <- p_recall(ac_tau, params_fit$tau_fit, model_params$s)

  # d
  ac_d <- map2_dbl(d_pred_seqs, params_fit$d_fit, function (x, d_x) {
    activation(x$time_within, x$time_between, model_params$h, d_x)
  })
  p_recall_d <- p_recall(ac_d, model_params$tau, model_params$s)
  
  # h
  ac_h <- map2_dbl(d_pred_seqs, params_fit$h_fit, function (x, h_x) {
    activation(x$time_within, x$time_between, h_x, model_params$decay)
  })
  p_recall_h <- p_recall(ac_h, model_params$tau, model_params$s)
  
  return (data.table(subset = subset,
                     sub_label = sub_label,
                     window_id = 0,
                     n_windows = 20,
                     window = 0,
                     geom_mean = NA,
                     window_type = "lm",
                     id = d_pred$id,
                     time_between = d_pred$time_between,
                     correct = d_pred$correct,
                     p_recall_tau = p_recall_tau,
                     p_recall_d = p_recall_d,
                     p_recall_h = p_recall_h))
  
}


pred_lm_path <- here("data", "pred_lm.csv")

if (!use_saved_predictions | !file.exists(pred_lm_path)) {
  pred_lm <- future_map(seq_len(nrow(lm_fit)), function (x) {
    lm_fit[x, predict_lm(subset, sub_label, lm_tau[[1]], lm_d[[1]], lm_h[[1]])]
  }) |>
    rbindlist()
  fwrite(pred_lm, pred_lm_path)
} else {
  pred_lm <- fread(pred_lm_path)
}

preds <- rbind(preds, pred_lm)

Visualise fit

plot_comparison <- function (d_model,
                             d_last,
                             window_range,
                             n_w = 1,
                             label_pos = list(data = list(x = 35000, y = .54), 
                                              model = list(x = 35000, y = .46)),
                             print_plot = TRUE) {
  
  
  plot_dodge <- function(y, dodge = .1) {
    return (y * (1 + dodge) - dodge/2)
  }
  
  p <- ggplot() +
    # Window background
    geom_rect(data = window_range[n_windows == n_w],
              aes(xmin = start/60, xmax = ifelse(is.na(shift(start, -1)), end, shift(start, -1))/60,
                  ymin = -Inf, ymax = Inf, alpha = as.factor(window)),
              fill = window_col) +
    # Jittered observations along edges
    geom_point(data = d_last, 
               aes(x = time_between/60, y = plot_dodge(correct, .05)),
               position = position_jitter(width = 0, height = .025, seed = 123),
               colour = obs_col, size = .001, pch = ".", alpha = .1) +
    # Predictions of the model
    # geom_point(data = d_model, 
    #            aes(x = time_between/60, y = pred_correct),
    #            colour = pred_col, alpha = .01) +
    # GAM: data
    geom_smooth(data = d_last,
                aes(x = time_between/60, y = correct),
                method = "gam", formula = y ~ s(x, bs = "cs"),
                colour = obs_col, lty = 1, lwd = 1) +
    # GAM: model
    geom_smooth(data = d_model, 
                aes(x = time_between/60, y = pred_correct),
                method = "gam", formula = y ~ s(x, bs = "cs"), 
                colour = pred_col, fill = pred_col, lty = 1, lwd = .75) +
    # Labels
    annotate("text", x = label_pos$data$x, y = label_pos$data$y,
             label = "Data", colour = obs_col) +
    annotate("text", x = label_pos$model$x, y = label_pos$model$y,
             label = "Model", colour = pred_col) +
    # Plot setup
    scale_x_log10(
      breaks = scales::trans_breaks("log10", function(x) 10^x),
      labels = scales::trans_format("log10", scales::math_format(10^.x)),
      expand = c(0, 0),
      sec.axis = sec_axis(~.x, breaks = label_x, labels = label_txt)
    ) +
    scale_y_continuous(breaks = seq(0, 1, by = .25), labels = scales::percent_format()) +
    scale_alpha_manual(values = rep(c(.1, .25), ceiling(n_w/2))) +
    guides(colour = "none",
           alpha = "none") +
    labs(x = "Between-session interval (minutes)",
         y = "Response accuracy") +
    annotation_logticks(sides = "b", outside = T) +
    coord_cartesian(ylim = c(0, 1), xlim = c(window_range[1, start], window_range[.N, end])/60, clip = "off") +
    theme_bw(base_size = 14) +
    theme(plot.margin = margin(7, 14, 7, 7),
          panel.grid.major.x = element_blank(),
          panel.grid.minor = element_blank(),
          panel.border = element_blank(),
          axis.text.x = element_text(margin = margin(t = 8)),
          axis.text.x.top = element_text(margin = margin(b = 8)))
  
  if (print_plot) print(p)
  return (p)
    
}

Regular fit

Window splits

Tau fitted to various window splits:

pred_tau <- copy(preds)
setnames(pred_tau, "p_recall_tau", "pred_correct")

p_tau_windows <- map(n_windows, function (n_w) {
  p <- plot_comparison(d_model = pred_tau[subset == "all" & n_windows == n_w & window_type == "regular"], 
                       d_last = pred_tau[subset == "all" & n_windows == n_w & window_type == "regular"],
                       window_range = window_range[n_windows == n_w & window_type == "regular"],
                       n_w = n_w, 
                       label_pos = list(data = list(x = 35000, y = .5), 
                                        model = list(x = 20000, y = .35)))
  return (p)
})

Decay fitted to various window splits:

pred_d <- copy(preds)
setnames(pred_d, "p_recall_d", "pred_correct")

p_d_windows <- map(n_windows, function (n_w) {
  p <- plot_comparison(d_model = pred_d[subset == "all" & n_windows == n_w & window_type == "regular"], 
                       d_last = pred_d[subset == "all" & n_windows == n_w & window_type == "regular"],
                       window_range = window_range[n_windows == n_w & window_type == "regular"],
                       n_w = n_w, 
                       label_pos = list(data = list(x = 35000, y = .5), 
                                        model = list(x = 20000, y = .35)))
  return (p)
})

Scaling factor fitted to various window splits:

pred_h <- copy(preds)
setnames(pred_h, "p_recall_h", "pred_correct")

p_h_windows <- map(n_windows, function (n_w) {
  p <- plot_comparison(d_model = pred_h[subset == "all" & n_windows == n_w & window_type == "regular"], 
                       d_last = pred_h[subset == "all" & n_windows == n_w & window_type == "regular"],
                       window_range = window_range[n_windows == n_w & window_type == "regular"],
                       n_w = n_w, 
                       label_pos = list(data = list(x = 35000, y = .5), 
                                        model = list(x = 20000, y = .35)))
  return (p)
})

Short intervals

Tau fitted to short intervals:

p_tau_short <- plot_comparison(d_model = pred_tau[subset == "all" & window_type == "short"], 
                               d_last = pred_tau[subset == "all" & window_type == "short"], 
                               window_range = window_range[n_windows == 1 & window_type == "regular"], 
                               n_w = 1, 
                               label_pos = list(data = list(x = 35000, y = .5), 
                                                model = list(x = 35000, y = .08)),
                               print_plot = FALSE) +
  geom_rect(aes(xmin  = window_range[window_type == "short", start/60], xmax = window_range[window_type == "short", end/60], ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_tau_short

Decay fitted to short intervals:

p_d_short <- plot_comparison(d_model = pred_d[subset == "all" & window_type == "short"], 
                             d_last = pred_d[subset == "all" & window_type == "short"], 
                             window_range = window_range[n_windows == 1 & window_type == "regular"], 
                             n_w = 1, 
                             label_pos = list(data = list(x = 35000, y = .5), 
                                              model = list(x = 35000, y = .08)),
                             print_plot = FALSE) +
  geom_rect(aes(xmin  = window_range[window_type == "short", start/60], xmax = window_range[window_type == "short", end/60], ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_d_short

Scaling factor fitted to short intervals:

p_h_short <- plot_comparison(d_model = pred_h[subset == "all" & window_type == "short"], 
                             d_last = pred_h[subset == "all" & window_type == "short"], 
                             window_range = window_range[n_windows == 1 & window_type == "regular"], 
                             n_w = 1, 
                             label_pos = list(data = list(x = 35000, y = .5), 
                                              model = list(x = 35000, y = .08)),
                             print_plot = FALSE) +
  geom_rect(aes(xmin  = window_range[window_type == "short", start/60], xmax = window_range[window_type == "short", end/60], ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_h_short

24h intervals

Tau fitted to 24 h:

p_tau_24h <- plot_comparison(d_model = pred_tau[subset == "all" & window_type == "24h"], 
                             d_last = pred_tau[subset == "all" & window_type == "24h"], 
                             window_range = window_range[n_windows == 1 & window_type == "regular"], 
                             n_w = 1, 
                             label_pos = list(data = list(x = 35000, y = .5), 
                                              model = list(x = 5000, y = .29)),
                             print_plot = FALSE) +
  geom_rect(aes(xmin  = window_range[window_type == "24h", start/60], xmax = window_range[window_type == "24h", end/60], ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_tau_24h

Decay fitted to 24 h:

p_d_24h <- plot_comparison(d_model = pred_d[subset == "all" & window_type == "24h"], 
                           d_last = pred_d[subset == "all" & window_type == "24h"], 
                           window_range = window_range[n_windows == 1 & window_type == "regular"], 
                           n_w = 1, 
                           label_pos = list(data = list(x = 35000, y = .5), 
                                            model = list(x = 5000, y = .78)),
                           print_plot = FALSE) +
  geom_rect(aes(xmin  = window_range[window_type == "24h", start/60], xmax = window_range[window_type == "24h", end/60], ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_d_24h

Scaling factor fitted to 24 h:

p_h_24h <- plot_comparison(d_model = pred_h[subset == "all" & window_type == "24h"], 
                           d_last = pred_h[subset == "all" & window_type == "24h"], 
                           window_range = window_range[n_windows == 1 & window_type == "regular"], 
                           n_w = 1, 
                           label_pos = list(data = list(x = 35000, y = .5), 
                                            model = list(x = 5000, y = .78)),
                           print_plot = FALSE) +
  geom_rect(aes(xmin  = window_range[window_type == "24h", start/60], xmax = window_range[window_type == "24h", end/60], ymin = -0.05, ymax = 1.05), fill = section_col, alpha = .25)

p_h_24h

Parametric fit

Tau fitted using the parametric function (tau(t)):

p_tau_lm <- plot_comparison(d_model = pred_tau[subset == "all" & window_type == "lm"], 
                            d_last = pred_tau[subset == "all" & window_type == "lm"], 
                            window_range = window_range[n_windows == 20 & window_type == "regular"], 
                            n_w = 20, 
                            label_pos = list(data = list(x = 35000, y = .5), 
                                             model = list(x = 5000, y = .7)))

Decay fitted using the parametric function (d(t)):

p_d_lm <- plot_comparison(d_model = pred_d[subset == "all" & window_type == "lm"], 
                          d_last = pred_d[subset == "all" & window_type == "lm"], 
                          window_range = window_range[n_windows == 20 & window_type == "regular"], 
                          n_w = 20, 
                          label_pos = list(data = list(x = 35000, y = .25), 
                                           model = list(x = 20000, y = .6)))

Scaling factor fitted using the parametric function (h(t)):

p_h_lm <- plot_comparison(d_model = pred_h[subset == "all" & window_type == "lm"], 
                          d_last = pred_h[subset == "all" & window_type == "lm"], 
                          window_range = window_range[n_windows == 20 & window_type == "regular"], 
                          n_w = 20, 
                          label_pos = list(data = list(x = 35000, y = .5), 
                                           model = list(x = 20000, y = .35)))

Fit by learner

At the level of the individual learner, the data is quite sparse. The plot below provides a sample of learner-specific fits for tau, based on a 20-window split. The red points are individual predictions (light) and averages (dark); the black points the observed recall.

set.seed(0)

pred_20_learner <- copy(preds[subset == "by_learner" & n_windows == 20 & window_type == "regular"])
pred_20_learner_avg <- pred_20_learner[, .(correct = mean(correct), p_recall_tau = mean(p_recall_tau), p_recall_d = mean(p_recall_d), p_recall_h = mean(p_recall_h)), by = .(sub_label, n_windows, window_type, window, geom_mean)]

sample_learners <- sample(unique(pred_20_learner_avg$sub_label), 18, replace = FALSE)

ggplot(pred_20_learner[sub_label %in% sample_learners], aes(x = time_between/60, y = p_recall_tau, group = sub_label)) +
  facet_wrap(~ sub_label, ncol = 6) +
  geom_point(aes(y = correct), position = position_jitter(width = 0, height = .025), alpha = .1, colour = "black") +
  geom_point(alpha = .05, colour = "red") +
  geom_point(data = pred_20_learner_avg[sub_label %in% sample_learners], aes( x= geom_mean/60, y = p_recall_tau), colour = "red", size = 2) +
  plot_timescales() +
  guides(colour = "none") +
  labs(x = "Between-session interval (min)", y = "Predicted recall", colour = "Learner")

The same plot fot fitted decay:

ggplot(pred_20_learner[sub_label %in% sample_learners], aes(x = time_between/60, y = p_recall_d, group = sub_label)) +
  facet_wrap(~ sub_label, ncol = 6) +
  geom_point(aes(y = correct), position = position_jitter(width = 0, height = .025), alpha = .1, colour = "black") +
  geom_point(alpha = .05, colour = "red") +
  geom_point(data = pred_20_learner_avg[sub_label %in% sample_learners], aes( x= geom_mean/60, y = p_recall_d), colour = "red", size = 2) +
  plot_timescales() +
  guides(colour = "none") +
  labs(x = "Between-session interval (min)", y = "Predicted recall", colour = "Learner")

The same plot for fitted scaling factor h:

ggplot(pred_20_learner[sub_label %in% sample_learners], aes(x = time_between/60, y = p_recall_h, group = sub_label)) +
  facet_wrap(~ sub_label, ncol = 6) +
  geom_point(aes(y = correct), position = position_jitter(width = 0, height = .025), alpha = .1, colour = "black") +
  geom_point(alpha = .05, colour = "red") +
  geom_point(data = pred_20_learner_avg[sub_label %in% sample_learners], aes( x= geom_mean/60, y = p_recall_h), colour = "red", size = 2) +
  plot_timescales() +
  guides(colour = "none") +
  labs(x = "Between-session interval (min)", y = "Predicted recall", colour = "Learner")

Fit by practice

Fitted tau by amount of practice:

pred_20_practice <- copy(preds[subset == "by_practice" & n_windows == 20 & window_type == "regular"])
pred_20_practice_avg <- pred_20_practice[, .(correct = mean(correct), p_recall_tau = mean(p_recall_tau), p_recall_d = mean(p_recall_d), p_recall_h = mean(p_recall_h)), by = .(sub_label, n_windows, window_type, window, geom_mean)]

ggplot(pred_20_practice_avg, aes(x = geom_mean/60, y = p_recall_tau, group = sub_label, colour = sub_label)) +
  facet_wrap(~ sub_label, ncol = 6) +
  geom_line() +
  geom_point(aes(colour = sub_label)) +
  plot_timescales() +
  labs(x = "Between-session interval (min)", y = "Predicted recall", colour = "Trials")

Fitted decay by amount of practice:

ggplot(pred_20_practice_avg, aes(x = geom_mean/60, y = p_recall_d, group = sub_label, colour = sub_label)) +
  facet_wrap(~ sub_label, ncol = 6) +
  geom_line() +
  geom_point(aes(colour = sub_label)) +
  plot_timescales() +
  labs(x = "Between-session interval (min)", y = "Predicted recall", colour = "Trials")

Fitted h by amount of practice:

ggplot(pred_20_practice_avg, aes(x = geom_mean/60, y = p_recall_h, group = sub_label, colour = sub_label)) +
  facet_wrap(~ sub_label, ncol = 6) +
  geom_line() +
  geom_point(aes(colour = sub_label)) +
  plot_timescales() +
  labs(x = "Between-session interval (min)", y = "Predicted recall", colour = "Trials")

Were some amounts of prior practice more prevalent in certain windows? Not really, the distribution looks fairly similar across different amounts of practice (facets):

ggplot(pred_20_practice, aes(x = window, fill = as.factor(window))) +
  facet_wrap(~ sub_label, ncol = 6, scales = "free_y") +
  geom_histogram() +
  labs(x = "Between-session interval (window)", y = "Count", fill = "Window")
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Goodness of fit

Log-likelihood

The log-likelihood provides a measure of the goodness of fit, with higher values being better. Since the various fitting methods lead to slightly differently sized subsets of the data, use the average log-likelihood (divided by the number of observations).

ll <- preds[, .(ll_tau = log_likelihood(correct, p_recall_tau, average = TRUE),
                ll_d = log_likelihood(correct, p_recall_d, average = TRUE),
                ll_h = log_likelihood(correct, p_recall_h, average = TRUE)), by = .(subset, n_windows, window_type)]

ll[, fit_config := paste(subset, n_windows, window_type)]
ll_long <- melt(ll, measure.vars = patterns("ll_*"), value.name = "ll")
ll_long[, variable := gsub("ll_", "", variable, fixed = TRUE)]
ggplot(ll_long, aes(x = tidytext::reorder_within(fit_config, -ll, variable), y = ll, colour = as.factor(n_windows))) +
  facet_grid(~ variable, scales = "free_x") +
  geom_point() +
  tidytext::scale_x_reordered() +
  labs(x = "Model", y = "Average log-likelihood (higher is better)", colour = "Windows") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

AIC

Log-likelihood does not take the complexity of the model into account. We can account for this using a metric like the Akaike Information Criterion (AIC), which evaluates the goodness of fit of a model while penalising for the number of parameters. Once again, since subset sizes are slightly different, we average AIC.

number_of_parameters <- preds[, .(k = uniqueN(window)), by = .(n_windows, window_type, subset, sub_label)]
number_of_parameters <- number_of_parameters[, .(k = sum(k)), by = .(n_windows, window_type, subset)]
number_of_parameters[window_type == "lm", k := k * 3] # Intercept, slope, error variance
preds <- preds[number_of_parameters, on = .(n_windows, window_type, subset)]

aic_pred <- preds[, .(n = .N,
                      aic_tau = aic(k[1], correct, p_recall_tau, average = TRUE),
                      aic_d = aic(k[1], correct, p_recall_d, average = TRUE),
                      aic_h = aic(k[1], correct, p_recall_h, average = TRUE)), by = .(subset, n_windows, window_type, k)]

aic_pred[, fit_config := paste(subset, n_windows, window_type)]
aic_long <- melt(aic_pred, measure.vars = patterns("aic_*"), value.name = "aic")
aic_long[, variable := gsub("aic_", "", variable, fixed = TRUE)]
ggplot(aic_long, aes(x = tidytext::reorder_within(fit_config, aic, variable), y = aic, colour = k)) +
  facet_grid(~ variable, scales = "free_x") +
  geom_point() +
  tidytext::scale_x_reordered() +
  labs(x = "Model", y = "Normalised AIC (lower is better)", colour = "Parameters") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

There are some differences in normalised AIC between parameters. To get a general sense of the best model, average AIC across parameters:

aic_pred[, aic_mean := (aic_tau + aic_d + aic_h) / 3]
ggplot(aic_pred, aes(x = reorder(fit_config, aic_mean), y = aic_mean, colour = k)) +
  geom_point() +
  labs(x = "Model", y = "Normalised AIC (lower is better)", colour = "Parameters") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

Akaike weights

To determine the relative support for each model, we calculate Akaike weights (Wagenmakers & Farrell, 2004) based on the AIC values. These sum to 1 and show the relative likelihood of each model given the data.

aic_pred[, akaike_weights_tau := akaike_weights(aic_tau)]
aic_pred[, akaike_weights_d := akaike_weights(aic_d)]
aic_pred[, akaike_weights_h := akaike_weights(aic_h)]
aw_long <- melt(aic_pred, measure.vars = patterns("akaike_weights_*"), value.name = "akaike_weights")
aw_long[, variable := gsub("akaike_weights_", "", variable, fixed = TRUE)]
ggplot(aw_long, aes(x = tidytext::reorder_within(fit_config, -akaike_weights, variable), y = akaike_weights, colour = k)) +
  facet_grid(~ variable, scales = "free_x") +
  geom_point() +
  tidytext::scale_x_reordered() +
  labs(x = "Model", y = "Relative likelihood", colour = "Parameters") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

Best model regardless of parameter:

ggplot(aw_long, aes(x = reorder(fit_config, -akaike_weights), y = akaike_weights, colour = variable)) +
  geom_point() +
  tidytext::scale_x_reordered() +
  labs(x = "Model", y = "Relative likelihood", colour = "Model\nParameter") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

ROC

To assess the overall ability of each model variant to distinguish between correct and incorrect responses, we can calculate the area under the receiver operating characteristic (ROC) curve (AUC). Whereas log-likelihood-based metrics require a specific threshold to determine correct vs. incorrect responses (i.e., p(recall) = 0.5), the ROC curve is threshold-independent.

library(pROC)
Type 'citation("pROC")' for a citation.

Attaching package: 'pROC'
The following objects are masked from 'package:stats':

    cov, smooth, var
roc_tau <- preds[, .( roc = list(roc(correct, p_recall_tau, quiet = TRUE))), by = .(subset, n_windows, window_type)]
roc_d <- preds[, .( roc = list(roc(correct, p_recall_d, quiet = TRUE))), by = .(subset, n_windows, window_type)]
roc_h <- preds[, .( roc = list(roc(correct, p_recall_h, quiet = TRUE))), by = .(subset, n_windows, window_type)]

ggroc(roc_tau$roc) +
  annotate("segment", x = 1, xend = 0, y = 0, yend = 1, linetype="dashed") +
  scale_colour_discrete(name = "Model", labels = roc_tau[, paste0(subset, " (", n_windows, " windows, ", window_type, ")")]) +
  labs(x = "False positive rate", y = "True positive rate", title = "ROC curve for tau") +
  theme_bw(base_size = 14)

ggroc(roc_d$roc) +
  annotate("segment", x = 1, xend = 0, y = 0, yend = 1, linetype="dashed") +
  scale_colour_discrete(name = "Model", labels = roc_tau[, paste0(subset, " (", n_windows, " windows, ", window_type, ")")]) +
  labs(x = "False positive rate", y = "True positive rate", title = "ROC curve for d") +
  theme_bw(base_size = 14)

ggroc(roc_h$roc) +
  annotate("segment", x = 1, xend = 0, y = 0, yend = 1, linetype="dashed") +
  scale_colour_discrete(name = "Model", labels = roc_tau[, paste0(subset, " (", n_windows, " windows, ", window_type, ")")]) +
  labs(x = "False positive rate", y = "True positive rate", title = "ROC curve for h") +
  theme_bw(base_size = 14)

The area under the curve (AUC) provides a single value summarising the ROC curve.

roc_tau[, auc := sapply(roc, function (x) x$auc)]
roc_d[, auc := sapply(roc, function (x) x$auc)]
roc_h[, auc := sapply(roc, function (x) x$auc)]
ggplot(roc_tau, aes(x = reorder(paste(subset, n_windows, window_type), -auc), y = auc)) +
  geom_point() +
  labs(x = "Model", y = "AUC", title = "AUC for tau") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

ggplot(roc_d, aes(x = reorder(paste(subset, n_windows, window_type), -auc), y = auc)) +
  geom_point() +
  labs(x = "Model", y = "AUC", title = "AUC for d") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

ggplot(roc_h, aes(x = reorder(paste(subset, n_windows, window_type), -auc), y = auc)) +
  geom_point() +
  labs(x = "Model", y = "AUC", title = "AUC for h") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

These AUC values show that the models have a reasonably good ability to distinguish between correct and incorrect responses, with the best model configurations achieving an ROC AUC of around 0.75 and the worst configuration only achieving chance level (0.50).

Bin-wise log-likelihood

Instead of calculating log-likelihood across bins, we can also calculate it separately within each bin of the 20-bin division. That way, we can (i) compare model performance at specific time scales, and (ii) correct for overrepresentation of certain time scales in the data.

windows_20 <- window_range[n_windows == 20]

ll_by_window <- map(1:20, function (i) {
  
  window_start <- windows_20[window == i, start]
  window_end <- windows_20[window == i, end]

  # Only include sequences within the window bounds: <start, end]
  # But: if this is the first window, do include the lower bound
  if (i == 1) {
    preds_window <- preds[time_between >= window_start & time_between <= window_end, ]
  } else {
    preds_window <- preds[time_between > window_start & time_between <= window_end, ]
  }
    
  ll_window <- preds_window[, .(window = i,
                                ll_tau = log_likelihood(correct, p_recall_tau, average = TRUE),
                                ll_d = log_likelihood(correct, p_recall_d, average = TRUE),
                                ll_h = log_likelihood(correct, p_recall_h, average = TRUE)), by = .(subset, n_windows, window_type)]
  
  ll_window[, fit_config := paste(subset, n_windows, window_type)]

  ll_window_long <- melt(ll_window, measure.vars = patterns("ll_*"), value.name = "ll")
  ll_window_long[, variable := gsub("ll_", "", variable, fixed = TRUE)]


  return (ll_window_long)
}) |>
  rbindlist()

ll_by_window <- ll_by_window[number_of_parameters, on = .(n_windows, window_type, subset)]

The ll by window is already normalised, i.e., divided by the number of observations in that window. That means that we can simply take the mean across windows to get an overall normalised ll:

ll_across_windows <- ll_by_window[, .(nll = mean(ll)), by = .(fit_config, subset, n_windows, window_type, variable, k)]

n_obs <- preds[, .N, by = .(subset, n_windows, window_type)]
ll_across_windows <- ll_across_windows[n_obs, on = .(subset, n_windows, window_type)]

aic_across_windows <- ll_across_windows[, .(aic = -2 * nll + (2 * k) / N), by = .(fit_config, k, subset, n_windows, window_type, variable)]

ggplot(aic_across_windows, aes(y = tidytext::reorder_within(fit_config, aic, variable), x = aic, colour = k)) +
  facet_wrap(~ variable, scales = "free") +
  geom_point() +
  tidytext::scale_y_reordered() +
  labs(y = "Model", x = "Normalised AIC (lower is better)", colour = "Parameters")

# Calculate Akaike weights from AIC per variable
aic_across_windows[, akaike_weight := akaike_weights(aic), by = variable]
ggplot(aic_across_windows, aes(x = tidytext::reorder_within(fit_config, -akaike_weight, variable), y = akaike_weight, colour = as.factor(n_windows))) +
  facet_grid(~ variable, scales = "free_x") +
  geom_point() +
  tidytext::scale_x_reordered() +
  scale_colour_viridis_d(option = "D") +
  labs(x = "Model", y = "Relative likelihood", colour = "Parameters") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))

Visualisations

Model fits (Figure 1):

p_combined <-
  (
    p_histogram + ggtitle("Interval distribution") |
    p_tau_short + ggtitle("Threshold optimised for 0–10 min")
  ) /
  (
    p_tau_24h + ggtitle("Threshold optimised for 24h") |
    p_tau_windows[[4]] + ggtitle("Interval-dependent threshold")
  ) /
  (
    p_d_24h + ggtitle("Decay optimised for 24h") |
    p_d_windows[[4]] + ggtitle("Interval-dependent decay")
  ) /
  (
    p_h_24h + ggtitle("Scaling factor optimised for 24h") |
    p_h_windows[[4]] + ggtitle("Interval-dependent scaling factor")
  ) +
  plot_annotation(tag_levels = "a") &  # automatically "a"..."h"
  theme(
    plot.background = element_rect(fill = "white", colour = NA),
    plot.tag.position = c(0, .976),       # top-left corner of each panel
    plot.tag = element_text(face = "bold", hjust = 0),
    plot.title = element_text(face = "bold", hjust = 0)
  )


ggsave(file.path("..", "output", "model_fitting_results.png"), width = 10, height = 15)

Parameters as a function of the between-session interval (Figure 2):

p_combined_time <-
  (
    p_tau_time + ggtitle("Interval-dependent threshold") |
    p_d_time   + ggtitle("Interval-dependent decay") |
    p_h_time   + ggtitle("Interval-dependent h")
  ) +
  plot_annotation(tag_levels = "a") &
  theme(
    plot.background = element_rect(fill = "white", colour = NA),
    plot.tag.position = c(0, .976),
    plot.tag = element_text(face = "bold", hjust = 0),
    plot.title = element_text(face = "bold", hjust = 0)
  )

ggsave(file.path("..", "output", "params_time.png"), width = 12, height = 4)
Warning in scale_x_log10(breaks = scales::trans_breaks("log10", function(x) 10^x), : log-10 transformation introduced infinite values.
log-10 transformation introduced infinite values.
Warning in scale_y_log10(): log-10 transformation introduced infinite values.
Warning in scale_x_log10(breaks = scales::trans_breaks("log10", function(x)
10^x), : log-10 transformation introduced infinite values.
Warning in scale_y_log10(): log-10 transformation introduced infinite values.

Session info

sessionInfo()
R version 4.4.3 (2025-02-28)
Platform: aarch64-apple-darwin20
Running under: macOS 26.2

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/Amsterdam
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] pROC_1.18.5       tidyr_1.3.1       ggtext_0.1.2      patchwork_1.3.0  
 [5] ggplot2_3.5.1     here_1.0.1        furrr_0.3.1       future_1.34.0    
 [9] purrr_1.0.4       data.table_1.17.0

loaded via a namespace (and not attached):
 [1] janeaustenr_1.0.0 sass_0.4.9        generics_0.1.3    xml2_1.3.8       
 [5] stringi_1.8.7     lattice_0.22-6    listenv_0.9.1     digest_0.6.37    
 [9] magrittr_2.0.3    evaluate_1.0.3    grid_4.4.3        fastmap_1.2.0    
[13] plyr_1.8.9        Matrix_1.7-3      rprojroot_2.0.4   jsonlite_2.0.0   
[17] tidytext_0.4.2    mgcv_1.9-1        viridisLite_0.4.2 scales_1.3.0     
[21] codetools_0.2-20  textshaping_1.0.0 jquerylib_0.1.4   cli_3.6.4        
[25] rlang_1.1.5       crayon_1.5.3      tokenizers_0.3.0  parallelly_1.43.0
[29] splines_4.4.3     munsell_0.5.1     withr_3.0.2       cachem_1.1.0     
[33] yaml_2.3.10       tools_4.4.3       parallel_4.4.3    dplyr_1.1.4      
[37] colorspace_2.1-1  globals_0.16.3    vctrs_0.6.5       R6_2.6.1         
[41] lifecycle_1.0.4   ragg_1.3.3        pkgconfig_2.0.3   pillar_1.10.1    
[45] bslib_0.9.0       gtable_0.3.6      glue_1.8.0        Rcpp_1.0.14      
[49] systemfonts_1.2.1 xfun_0.51         tibble_3.2.1      tidyselect_1.2.1 
[53] rstudioapi_0.17.1 knitr_1.50        farver_2.1.2      SnowballC_0.7.1  
[57] nlme_3.1-168      htmltools_0.5.8.1 labeling_0.4.3    rmarkdown_2.29   
[61] compiler_4.4.3    gridtext_0.1.5   
LS0tCnRpdGxlOiAiRml0IG1lbW9yeSBtb2RlbHMiCmF1dGhvcjogIk1hYXJ0ZW4gdmFuIGRlciBWZWxkZSIKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgc21hcnQ6IG5vCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICBnaXRodWJfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCiMgT3V0bGluZQoKVGhpcyBub3RlYm9vayBmaXRzIHZhcmlvdXMgY29uZmlndXJhdGlvbnMgb2YgdGhlIG1lbW9yeSBtb2RlbCB0byB0aGUgZnVsbCBzZXQgb2YgcmV0cmlldmFsIHByYWN0aWNlIGRhdGEuCldlIHZhcnkgdGhlIGZvbGxvd2luZyBmYWN0b3JzOgoKICAtICoqU3Vic2V0Kio6IGZpdCBhbGwgZGF0YSwgZml0IGJ5IGxlYXJuZXIsIGZpdCBieSBhbW91bnQgb2YgcHJhY3RpY2UKICAtICoqVGVtcG9yYWwgc2NvcGUqKjogZml0IGFsbCBpbnRlcnZhbHMsIG9ubHkgc2hvcnQgaW50ZXJ2YWxzICgwLTEwIG1pbiksIG9ubHkgaW50ZXJ2YWxzIGFyb3VuZCAyNCBoLCBkaWZmZXJlbnQgbnVtYmVycyBvZiB0aW1lIGJpbnMKICAtICoqUGFyYW1ldGVyKio6IGZpdCByZXRyaWV2YWwgdGhyZXNob2xkICRcdGF1JCwgZGVjYXkgJGQkLCBzY2FsaW5nIGZhY3RvciAkaCQKICAKRmluYWxseSwgdGhlIGZpdHRlZCBtb2RlbHMgYXJlIGNvbXBhcmVkIGluIHRlcm1zIG9mIGdvb2RuZXNzIG9mIGZpdC4gClNpbmNlIHRoZSBmaXRzIHVzZSBkaWZmZXJlbnQgbnVtYmVycyBvZiBwYXJhbWV0ZXJzIGFuZCBzbGlnaHRseSBkaWZmZXJlbnQgc3Vic2V0cyBvZiB0aGUgZnVsbCBkYXRhc2V0LCB0aGUgY29tcGFyaXNvbiB1c2VzIGF2ZXJhZ2UgQWthaWtlIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoQUlDKSBzY29yZXMuCgogIAojIFNldHVwCgpgYGB7ciBzZXR1cH0KbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGZ1cnJyKQpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoZ2d0ZXh0KQoKc291cmNlKCIwMF9oZWxwZXJfZnVucy5SIikKCnByZWRfY29sIDwtICIjQ0MzMzExIiAgICAjIHJlZApvYnNfY29sIDwtICIjMDAwMDAwIiAgICAgIyBibGFjawp3aW5kb3dfY29sIDwtICIjMzNCQkVFIiAgIyBibHVlCnNlY3Rpb25fY29sIDwtICIjRkQ1MjBGIiAjIG9yYW5nZQoKZnV0dXJlOjpwbGFuKCJtdWx0aXNlc3Npb24iLCB3b3JrZXJzID0gOCkgIyBTZXQgdG8gZGVzaXJlZCBudW1iZXIgb2YgY29yZXMKCnNldC5zZWVkKDApCmBgYAoKCiMjIE1vZGVsIGZpdHRpbmcgc2V0dXAKClNldCBkZWZhdWx0IHBhcmFtZXRlcnMgZm9yIHRoZSBBQ1QtUiBtZW1vcnkgbW9kZWwuClRoZXNlIHZhbHVlcyBhcmUgdXNlZCB3aGVuZXZlciBhIHBhcmFtZXRlciBpcyBub3QgZml0dGVkLgpgYGB7ciBtb2RlbC1wYXJhbWV0ZXJzfQptb2RlbF9wYXJhbXMgPC0gbGlzdCgKICB0YXUgPSAtMy4wLCAjIFJldHJpZXZhbCB0aHJlc2hvbGQKICBzID0gLjUsICMgQWN0aXZhdGlvbiBub2lzZQogIGRlY2F5ID0gLjUsICMgRGVjYXkKICBoID0gMSAjIFNjYWxpbmcgZmFjdG9yCikKYGBgCgoKIyMgRGF0YSBzZXR1cAoKYGBge3IgbG9hZC1kYXRhfQpkX2YgPC0gZnJlYWQoZmlsZS5wYXRoKCIuLiIsICJkYXRhIiwgImNvZ3BzeWNoX2RhdGFfZm9ybWF0dGVkLmNzdiIpKQpgYGAKCkVhY2ggdXNlci1mYWN0IHBhaXIgaGFzIGEgc2luZ2xlIGxlYXJuaW5nIHNlcXVlbmNlIGFzc29jaWF0ZWQgd2l0aCBpdCwgY29uc2lzdGluZyBvZiB0aHJlZSBvciBtb3JlIHRyaWFscyBpbiBvbmUgc2Vzc2lvbiBhbmQgdGhlIGZpcnN0IHRyaWFsIGluIHRoZSBuZXh0IHNlc3Npb24uCgpJc29sYXRlIHRoZSBsYXN0IG9ic2VydmF0aW9uIHBlciBsZWFybmluZyBzZXF1ZW5jZSAoaS5lLiwgdGhlIG9uZSBhZnRlciB0aGUgYmV0d2Vlbi1zZXNzaW9uIGludGVydmFsKS4KVGhpcyBpcyB0aGUgb2JzZXJ2YXRpb24gdGhhdCB0aGUgbW9kZWwgaGFzIHRvIHByZWRpY3QsIGdpdmVuIGFsbCBwcmlvciBvYnNlcnZhdGlvbnMgaW4gdGhlIHNlcXVlbmNlLgpgYGB7ciBsYXN0LW9ic2VydmF0aW9ufQpkX2xhc3QgPC0gZF9mWywgLlNEWy5OXSwgYnkgPSBpZF0KYGBgCgojIyMgQ3JlYXRlIHN1YnNldHMKClNwbGl0IHRoZSBkYXRhIGJ5IGxlYXJuZXIsIHNvIHRoYXQgd2UgY2FuIGZpdCBlYWNoIGxlYXJuZXIncyBkYXRhIHNlcGFyYXRlbHkuCmBgYHtyIGRhdGEtYnktbGVhcm5lcn0KZF9mX2J5X2xlYXJuZXIgPC0gc3BsaXQoZF9mLCBieSA9ICJ1c2VyIikKZF9sYXN0X2J5X2xlYXJuZXIgPC0gc3BsaXQoZF9sYXN0LCBieSA9ICJ1c2VyIikKYGBgCgpTcGxpdCB0aGUgZGF0YSBieSB0aGUgYW1vdW50IG9mIHByYWN0aWNlIChpLmUuLCB0aGUgbnVtYmVyIG9mIHRyaWFscyB3aXRoaW4gYSBzZXF1ZW5jZSksIHNvIHRoYXQgd2UgY2FuIGZpdCBkaWZmZXJlbnQgYW1vdW50cyBvZiBwcmFjdGljZSBzZXBhcmF0ZWx5LgpFYWNoIHNlcXVlbmNlIGNvbnNpc3RzIG9mIGF0IGxlYXN0IDQgdHJpYWxzLgpUbyBrZWVwIHRoaW5ncyBtYW5hZ2VhYmxlLCB3ZSBncm91cCBzZXF1ZW5jZXMgd2l0aCBvdmVyIDIxIHRyaWFscyBpbnRvIGEgc2luZ2xlICIyMSsiIGJ1Y2tldC4KYGBge3IgZGF0YS1ieS1wcmFjdGljZX0KdHJpYWxzX2J5X2lkIDwtIGRfZlssIC4odHJpYWxzID0gLk4pLCBieSA9IC4oaWQpXQp0cmlhbHNfYnlfaWRbLCB0cmlhbHMgOj0gZmFjdG9yKGlmZWxzZSh0cmlhbHMgPCAyMSwgdHJpYWxzLCAiMjErIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYyg0OjIwLCAiMjErIikpXQoKZF9mIDwtIGRfZlt0cmlhbHNfYnlfaWQsIG9uID0gImlkIl0KZF9sYXN0IDwtIGRfbGFzdFt0cmlhbHNfYnlfaWQsIG9uID0gImlkIl0KCmRfZl9ieV9wcmFjdGljZSA8LSBzcGxpdChkX2YsIGJ5ID0gInRyaWFscyIpCmRfbGFzdF9ieV9wcmFjdGljZSA8LSBzcGxpdChkX2xhc3QsIGJ5ID0gInRyaWFscyIpCmBgYAoKIyMjIERlZmluZSB0aW1lIGJpbnMKClNwbGl0IHRoZSBkYXRhIGJ5IHRoZSBiZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwuClRoaXMgaW50ZXJ2YWwgcmFuZ2VzIGZyb20gc2Vjb25kcyB0byB3ZWVrcy4KV2UgdmFyeSB0aGUgbnVtYmVyIG9mIHNwbGl0cyAod2luZG93cykgYmV0d2VlbiAxIChhbGwgZGF0YSBpbiBhIHNpbmdsZSB3aW5kb3cpIGFuZCAyMC4KV2luZG93IHJhbmdlcyBhcmUgZXF1YWxseSBzaXplZCBvbiBhIGxvZ2FyaXRobWljIHNjYWxlOyB0aGUgbWlkZGxlIG9mIGVhY2ggd2luZG93IGlzIHRoZSBnZW9tZXRyaWMgbWVhbiBvZiBpdHMgYm91bmRhcmllcy4KCgpgYGB7ciB3aW5kb3ctcmFuZ2V9Cm5fd2luZG93cyA8LSBjKDEsIDUsIDEwLCAyMCkKCndpbmRvd19yYW5nZSA8LSBtYXAobl93aW5kb3dzLCBmdW5jdGlvbiAobl93KSB7CgogIGRfd2luZG93cyA8LSBjb3B5KGRfbGFzdCkKICAKICBpZiAobl93ID09IDEpIHsKICAgIGRfd2luZG93c1ssIHdpbmRvdyA6PSAxXQogIH0gZWxzZSB7CiAgICBkX3dpbmRvd3NbLCB3aW5kb3cgOj0gY3V0KGxvZyh0aW1lX2JldHdlZW4pLCBicmVha3MgPSBuX3csIGxhYmVscyA9IEZBTFNFKV0KICB9ICAKICAKICAjIEdldCB0aGUgd2luZG93IHJhbmdlKHMpCiAgd2luZG93X3JhbmdlIDwtIGRfd2luZG93c1ssIC4oc3RhcnQgPSBtaW4odGltZV9iZXR3ZWVuKSwgZW5kID0gbWF4KHRpbWVfYmV0d2VlbikpLCBieSA9IC4od2luZG93KV0KICB3aW5kb3dfcmFuZ2VbLCBnZW9tX21lYW4gOj0gc3FydChzdGFydCplbmQpLCBieSA9IC4od2luZG93KV0KICBzZXRvcmRlcih3aW5kb3dfcmFuZ2UsIHdpbmRvdykKICB3aW5kb3dfcmFuZ2VbLCB3aW5kb3cgOj0gd2luZG93XQogIHdpbmRvd19yYW5nZVssIG5fd2luZG93cyA6PSBuX3ddCgogIHJldHVybiAod2luZG93X3JhbmdlKQp9KSB8PgogIHJiaW5kbGlzdCgpCgp3aW5kb3dfcmFuZ2VbLCB3aW5kb3dfdHlwZSA6PSAicmVndWxhciJdCmBgYAoKV2UnbGwgYWxzbyBpbmNsdWRlIGEgInNob3J0IiB3aW5kb3cgKDAtMTAgbWluKSBhbmQgYSAiMjRoIiB3aW5kb3cgKDIzLjUtMjQuNWgpLCB0byBzZWUgaG93IHdlbGwgdGhlIG1vZGVsIHBlcmZvcm1zIGlmIGZpdHRlZCBvbmx5IHRvIHRoZXNlIGludGVydmFscy4KYGBge3Igc2hvcnQtYW5kLTI0aH0Kd2luZG93X3JhbmdlIDwtIHJiaW5kKHdpbmRvd19yYW5nZSwKICAgICAgICAgICAgICAgICAgICAgIGxpc3Qod2luZG93ID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnQgPSB3aW5kb3dfcmFuZ2VbLCBtaW4oc3RhcnQpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZW5kID0gMTAqNjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlb21fbWVhbiA9IHNxcnQoKHdpbmRvd19yYW5nZVssIG1pbihzdGFydCldKSAqICgxMCo2MCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX3dpbmRvd3MgPSAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgd2luZG93X3R5cGUgPSAic2hvcnQiKSwKICAgICAgICAgICAgICAgICAgICAgIGxpc3Qod2luZG93ID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnQgPSAyMy41KjYwKjYwLAogICAgICAgICAgICAgICAgICAgICAgICAgICBlbmQgPSAyNC41KjYwKjYwLAogICAgICAgICAgICAgICAgICAgICAgICAgICBnZW9tX21lYW4gPSBzcXJ0KDIzLjUqMjQuNSkqNjAqNjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fd2luZG93cyA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpbmRvd190eXBlID0gIjI0aCIpKQoKd2luZG93X3JhbmdlWywgd2luZG93X2lkIDo9IDE6Lk5dCmBgYAoKVGhlIHJlc3VsdGluZyB3aW5kb3cgcmFuZ2VzOgpgYGB7ciB3aW5kb3ctcmFuZ2VzfQp3aW5kb3dfcmFuZ2UKYGBgCgoKVGhlIGRpc3RyaWJ1dGlvbiBvZiBiZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWxzIGxvb2tzIGFzIGZvbGxvd3M6CmBgYHtyfQpwX2hpc3RvZ3JhbSA8LSBnZ3Bsb3QoKSArCiAgIyBXaW5kb3cgYmFja2dyb3VuZAogIGdlb21fcmVjdChkYXRhID0gd2luZG93X3JhbmdlWzFdLCBhZXMoeG1pbiA9IHN0YXJ0LzYwLCB4bWF4ID0gZW5kLzYwLCB5bWluID0gLUluZiwgeW1heCA9IEluZiksIGZpbGwgPSB3aW5kb3dfY29sLCBhbHBoYSA9IC4xKSArCiAgIyBIaXN0b2dyYW0KICBnZW9tX2hpc3RvZ3JhbShkYXRhID0gZF9sYXN0LCBhZXMoeCA9IHRpbWVfYmV0d2Vlbi82MCwgeSA9IC4ubmNvdW50Li4pLCBiaW5zID0gMTAwLCBmaWxsID0gb2JzX2NvbCkgKwogICMgUGxvdCBzZXR1cAogIHNjYWxlX3hfbG9nMTAoCiAgICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsIHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSwKICAgIGV4cGFuZCA9IGMoMCwgMCksCiAgICBzZWMuYXhpcyA9IHNlY19heGlzKH4ueCwgYnJlYWtzID0gbGFiZWxfeCwgbGFiZWxzID0gbGFiZWxfdHh0KQogICkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMSwgYnkgPSAuMjUpKSArCiAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWludXRlcykiLAogICAgICAgeSA9ICJEZW5zaXR5IikgKwogIGFubm90YXRpb25fbG9ndGlja3Moc2lkZXMgPSAiYiIsIG91dHNpZGUgPSBUKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsIDEpLCB4bGltID0gYyh3aW5kb3dfcmFuZ2VbMSwgc3RhcnRdLCB3aW5kb3dfcmFuZ2VbMSwgZW5kXSkvNjAsIGNsaXAgPSAib2ZmIikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUocGxvdC5tYXJnaW4gPSBtYXJnaW4oNywgMTQsIDcsIDcpLAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IDgpKSwKICAgICAgICBheGlzLnRleHQueC50b3AgPSBlbGVtZW50X3RleHQobWFyZ2luID0gbWFyZ2luKGIgPSA4KSkpCgpwX2hpc3RvZ3JhbQoKYGBgCgoKCiMgRml0IG1vZGVscwoKRGVmaW5lIHRoZSBtb2RlbCBmaXR0aW5nIGZ1bmN0aW9uOgpgYGB7ciBmaXQtbW9kZWx9CmZpdF9tb2RlbCA8LSBmdW5jdGlvbiAoc3Vic2V0ID0gYygiYWxsIiwgImJ5X2xlYXJuZXIiLCAiYnlfcHJhY3RpY2UiKSkgewoKICBzdWJzZXQgPC0gbWF0Y2guYXJnKHN1YnNldCkKICBtZXNzYWdlKCJTdWJzZXQ6ICIsIHN1YnNldCkKCiAgZF9maXQgPC0gc3dpdGNoKHN1YnNldCwKICAgICAgICAgICAgICAgICAgImFsbCIgPSBsaXN0KGNvcHkoZF9sYXN0KSksCiAgICAgICAgICAgICAgICAgICJieV9sZWFybmVyIiA9IGNvcHkoZF9sYXN0X2J5X2xlYXJuZXIpLAogICAgICAgICAgICAgICAgICAiYnlfcHJhY3RpY2UiID0gY29weShkX2xhc3RfYnlfcHJhY3RpY2UpKQogIAogICMgSXRlcmF0ZSBvdmVyIHN1YnNldHMKICBmaXRfb3V0IDwtIG1hcChkX2ZpdCwgZnVuY3Rpb24gKGRfZml0X3N1YikgewogICAgCiAgICAjIEl0ZXJhdGUgb3ZlciB3aW5kb3cgcmFuZ2VzCiAgICB3aW5kb3dzIDwtIGNvcHkod2luZG93X3JhbmdlKQogICAgCiAgICAjIE9ubHkgZml0IHRoZSAic2hvcnQiIGFuZCAiMjRoIiB3aW5kb3dzIHRvIGFsbCBzZXF1ZW5jZXMKICAgIGlmIChzdWJzZXQgIT0gImFsbCIpIHsKICAgICAgd2luZG93cyA8LSB3aW5kb3dzW3dpbmRvd190eXBlID09ICJyZWd1bGFyIl0KICAgIH0KICAgIAogICAgZml0X3dpbmRvd3MgPC0gc3BsaXQod2luZG93cywgYnkgPSAid2luZG93X2lkIikKICAgIGZpdF9wYXJhbXMgPC0gZnV0dXJlX21hcChmaXRfd2luZG93cywgZnVuY3Rpb24gKGZpdF93aW5kb3cpIHsKICAgICAgCiAgICAgICMgT25seSBpbmNsdWRlIHNlcXVlbmNlcyB3aXRoaW4gdGhlIHdpbmRvdyBib3VuZHM6IDxzdGFydCwgZW5kXQogICAgICAjIEJ1dDogaWYgdGhpcyBpcyB0aGUgZmlyc3Qgd2luZG93LCBkbyBpbmNsdWRlIHRoZSBsb3dlciBib3VuZAogICAgICBpZiAoZml0X3dpbmRvdyR3aW5kb3cgPT0gMSkgewogICAgICAgIGRfZml0X3N1Yl93aW5kb3cgPC0gZF9maXRfc3ViW3RpbWVfYmV0d2VlbiA+PSBmaXRfd2luZG93JHN0YXJ0ICYgdGltZV9iZXR3ZWVuIDw9IGZpdF93aW5kb3ckZW5kXQogICAgICB9IGVsc2UgewogICAgICAgIGRfZml0X3N1Yl93aW5kb3cgPC0gZF9maXRfc3ViW3RpbWVfYmV0d2VlbiA+IGZpdF93aW5kb3ckc3RhcnQgJiB0aW1lX2JldHdlZW4gPD0gZml0X3dpbmRvdyRlbmRdCiAgICAgIH0KICAgICAgCiAgICAgICMgVG8gaWRlbnRpZnkgcGFyYW1ldGVycywgcmVxdWlyZSBhdCBsZWFzdCAzIHJlc3BvbnNlcyBpbiB0aGUgd2luZG93LCB3aXRoIGEgbWl4IG9mIGNvcnJlY3QgYW5kIGluY29ycmVjdCByZXNwb25zZXMKICAgICAgaWYgKCEobnJvdyhkX2ZpdF9zdWJfd2luZG93KSA+PSAzICYmIGJldHdlZW4obWVhbihkX2ZpdF9zdWJfd2luZG93JGNvcnJlY3QpLCAwLCAxLCBpbmNib3VuZHMgPSBGQUxTRSkpKSB7CiAgICAgICAgcmV0dXJuIChOVUxMKQogICAgICB9CiAgICAgIAogICAgICAjIFByZXBhcmUgZGF0YQogICAgICBkX2ZpdF9zdWJfd2luZG93Wywgd2luZG93IDo9IGZpdF93aW5kb3ckd2luZG93XQogICAgICBkX2ZpdF9zdWJfd2luZG93Wywgc2VxdWVuY2UgOj0gMTouTl0KICAgICAgZF9maXRfc3ViX3dpbmRvdyA8LSBkX2ZbZF9maXRfc3ViX3dpbmRvd1ssIC4oaWQsIHdpbmRvdywgc2VxdWVuY2UpXSwgb24gPSAuKGlkKV0KICAgICAgZF9maXRfc3ViX3dpbmRvd19zZXFzIDwtIGdlbmVyYXRlX3NlcV9saXN0KGRfZml0X3N1Yl93aW5kb3cpCiAgICAgIAogICAgICAjIEZpdCBwYXJhbWV0ZXJzCiAgICAgIGRfZml0X3N1Yl93aW5kb3dfcGFyYW1zIDwtIGZpdF9wYXJhbWV0ZXJzKGRfZml0X3N1Yl93aW5kb3dfc2VxcywgbW9kZWxfcGFyYW1zKQogICAgICAKICAgICAgIyBBZGQgc3ViZ3JvdXAgaW5mbyBhbmQgd2luZG93IElECiAgICAgIHN1Yl9sYWJlbCA8LSBzd2l0Y2goc3Vic2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICJhbGwiID0gTkEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgImJ5X2xlYXJuZXIiID0gZF9maXRfc3ViWzEsIHVzZXJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICJieV9wcmFjdGljZSIgPSBkX2ZpdF9zdWJbMSwgdHJpYWxzXSkKICAgICAgCiAgICAgIGRfZml0X3N1Yl93aW5kb3dfcGFyYW1zIDwtIGNiaW5kKGZpdF93aW5kb3dbLCAuKHdpbmRvd19pZCldLCBzdWJfbGFiZWwsIGRfZml0X3N1Yl93aW5kb3dfcGFyYW1zKQogICAgICAKICAgICAgcmV0dXJuIChkX2ZpdF9zdWJfd2luZG93X3BhcmFtcykKICAgIH0pCiAgICAKICAgIHJldHVybiAoZml0X3BhcmFtcykKICB9LCAucHJvZ3Jlc3MgPSBpbnRlcmFjdGl2ZSgpKQogIAogICMgT25seSBrZWVwIHdpbmRvdyBmaXRzIHdpdGggb2JzZXJ2YXRpb25zCiAgZml0X291dCA8LSBkaXNjYXJkKGZsYXR0ZW4oZml0X291dCksIGlzLm51bGwpCiAgCiAgIyBDb252ZXJ0IGxpc3QgdG8gZGF0YS50YWJsZQogIGZpdF9vdXQgPC0gcmJpbmRsaXN0KGZpdF9vdXQpCiAgCiAgIyBBZGQgd2luZG93IGluZm9ybWF0aW9uCiAgZml0X291dCA8LSBmaXRfb3V0W3dpbmRvd19yYW5nZSwgb24gPSAuKHdpbmRvd19pZCldWyFpcy5uYShpZCldCgogICMgQWRkIHN1YnNldCBpbmZvcm1hdGlvbgogIGZpdF9vdXQgPC0gY2JpbmQoZGF0YS50YWJsZShzdWJzZXQgPSBzdWJzZXQpLCBmaXRfb3V0KQogIAogIHJldHVybiAoZml0X291dCkKICAKfQpgYGAKCkZpdCBhbGwgdmFyaWFudHMgb2YgdGhlIG1vZGVsIChub3RlOiB0aGlzIGNhbiB0YWtlIGEgd2hpbGUhKS4KKFNldCBgdXNlX3NhdmVkX2ZpdGAgdG8gVFJVRSB0byB0cnkgdG8gbG9hZCBwcmV2aW91cyBmaXRzIGZyb20gZmlsZS4pCmBgYHtyIGZpdH0KdXNlX3NhdmVkX2ZpdCA8LSBUUlVFCgpmaXRfYWxsX3BhdGggPC0gaGVyZSgiZGF0YSIsICJmaXRfYWxsLmNzdiIpCmZpdF9ieV9sZWFybmVyX3BhdGggPC0gaGVyZSgiZGF0YSIsICJmaXRfYnlfbGVhcm5lci5jc3YiKQpmaXRfYnlfcHJhY3RpY2VfcGF0aCA8LSBoZXJlKCJkYXRhIiwgImZpdF9ieV9wcmFjdGljZS5jc3YiKQoKaWYgKCF1c2Vfc2F2ZWRfZml0IHwgIWZpbGUuZXhpc3RzKGZpdF9hbGxfcGF0aCkpIHsKICBmaXRfYWxsIDwtIGZpdF9tb2RlbChzdWJzZXQgPSAiYWxsIikKICBmd3JpdGUoZml0X2FsbCwgZml0X2FsbF9wYXRoKQp9IGVsc2UgewogIGZpdF9hbGwgPC0gZnJlYWQoZml0X2FsbF9wYXRoKQp9CgppZiAoIXVzZV9zYXZlZF9maXQgfCAhZmlsZS5leGlzdHMoZml0X2J5X2xlYXJuZXJfcGF0aCkpIHsKICBmaXRfYnlfbGVhcm5lciA8LSBmaXRfbW9kZWwoc3Vic2V0ID0gImJ5X2xlYXJuZXIiKQogIGZ3cml0ZShmaXRfYnlfbGVhcm5lciwgZml0X2J5X2xlYXJuZXJfcGF0aCkKfSBlbHNlIHsKICBmaXRfYnlfbGVhcm5lciA8LSBmcmVhZChmaXRfYnlfbGVhcm5lcl9wYXRoKQp9CgppZiAoIXVzZV9zYXZlZF9maXQgfCAhZmlsZS5leGlzdHMoZml0X2J5X3ByYWN0aWNlX3BhdGgpKSB7CiAgZml0X2J5X3ByYWN0aWNlIDwtIGZpdF9tb2RlbChzdWJzZXQgPSAiYnlfcHJhY3RpY2UiKQogIGZ3cml0ZShmaXRfYnlfcHJhY3RpY2UsIGZpdF9ieV9wcmFjdGljZV9wYXRoKQp9IGVsc2UgewogIGZpdF9ieV9wcmFjdGljZSA8LSBmcmVhZChmaXRfYnlfcHJhY3RpY2VfcGF0aCkKfQoKZml0cyA8LSByYmluZChmaXRfYWxsLCBmaXRfYnlfbGVhcm5lciwgZml0X2J5X3ByYWN0aWNlKQpgYGAKCiMjIyBQYXJhbWV0cmljIGZ1bmN0aW9uCkluIGFkZGl0aW9uIHRvIHdpbmRvdy1zcGVjaWZpYyBmaXRzLCB3ZSBjYW4gYWxzbyBpbmNsdWRlIGEgbGluZWFyIG1vZGVsIGZpdCBiYXNlZCBvbiB0aGUgd2luZG93LXdpc2UgcGFyYW1ldGVyIGVzdGltYXRlcy4KV2Ugb25seSBkbyB0aGlzIGZvciB0aGUgMjAtd2luZG93IHNwbGl0LgpgYGB7ciBmaXQtcGFyYW1ldHJpY30KZml0X2J5X3dpbmRvdyA8LSBmaXRzW25fd2luZG93cyA9PSAyMCwgLih0YXUgPSB0YXVbMV0sIGQgPSBtZWRpYW4oZCksIGggPSBtZWRpYW4oaCkpLCBieSA9IC4oc3Vic2V0LCBzdWJfbGFiZWwsIG5fd2luZG93cywgd2luZG93KV0KZml0X2J5X3dpbmRvdyA8LSBmaXRfYnlfd2luZG93W3dpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMjAsIC4obl93aW5kb3dzLCB3aW5kb3csIGdlb21fbWVhbildLCBvbiA9IC4obl93aW5kb3dzLCB3aW5kb3cpXQoKIyBGaXQgYSBsaW5lYXIgbW9kZWwgZm9yIGVhY2ggc2V0IG9mIHBhcmFtZXRlciBlc3RpbWF0ZXMKZml0X2xtIDwtIGZ1bmN0aW9uIChnZW9tX21lYW4sIHBhcmFtLCBsb2dfcGFyYW0gPSBGQUxTRSkgewogIGlmIChsb2dfcGFyYW0pIHsKICAgIHBhcmFtIDwtIGxvZyhwYXJhbSkKICB9CiAgbSA8LSBsbShwYXJhbSB+IGxvZyhnZW9tX21lYW4pKQogIAogIHJldHVybiAobSkKfQoKbG1fZml0IDwtIGZpdF9ieV93aW5kb3dbLCAuKGxtX3RhdSA9IGxpc3QoZml0X2xtKGdlb21fbWVhbiwgdGF1KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG1fZCA9IGxpc3QoZml0X2xtKGdlb21fbWVhbiwgZCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxtX2ggPSBsaXN0KGZpdF9sbShnZW9tX21lYW4sIGgsIGxvZ19wYXJhbSA9IFRSVUUpKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSAuKHN1YnNldCwgc3ViX2xhYmVsKV0KYGBgCgoKIyMgRml0dGVkIHBhcmFtZXRlcnMKCiMjIyBSZWd1bGFyIGZpdAoKSGVyZSB3ZSBmaXQgYWxsIGxlYXJuZXJzIGFuZCBhbW91bnRzIG9mIHByYWN0aWNlIHRvZ2V0aGVyLgpSZWQgcG9pbnRzIHNob3cgdGhlIG1lZGlhbiBmaXR0ZWQgcGFyYW1ldGVyIHZhbHVlIGluIGVhY2ggdGltZSBiaW4uCmBgYHtyIGZpdHRlZC10YXV9CmZpdF9hbGxfYXZnIDwtIGZpdF9hbGxbLCAgLih0YXUgPSBtZWRpYW4odGF1KSwgZCA9IG1lZGlhbihkKSwgaCA9IG1lZGlhbihoKSksICBieSA9IC4obl93aW5kb3dzLCB3aW5kb3dfdHlwZSwgd2luZG93LCBnZW9tX21lYW4pXQoKcF90YXVfYWxsIDwtIGdncGxvdChmaXRfYWxsLCBhZXMoeCA9IGdlb21fbWVhbi82MCwgeSA9IHRhdSkpICsKICBmYWNldF93cmFwKH4gcGFzdGUwKG5fd2luZG93cywgIiB3aW5kb3cocykgKCIsIHdpbmRvd190eXBlLCAiKSIpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC4wMSkgKwogIGdlb21fc21vb3RoKGRhdGEgPSBmaXRfYWxsX2F2ZywgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgZm9ybXVsYSA9IHkgfiB4KSArCiAgZ2VvbV9wb2ludChkYXRhID0gZml0X2FsbF9hdmcsIGNvbG91ciA9ICJyZWQiKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbikiLCB5ID0gIkZpdHRlZCBwYXJhbWV0ZXIiLCB0aXRsZSA9ICJSZXRyaWV2YWwgdGhyZXNob2xkIHRhdSIpCgpnZ3NhdmUoaGVyZSgib3V0cHV0IiwgInRhdV9maXRfYWxsLnBuZyIpLCBwX3RhdV9hbGwsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpCmBgYAoKIVtdKC4uL291dHB1dC90YXVfZml0X2FsbC5wbmcpCgpgYGB7ciBmaXR0ZWQtZH0KcF9kX2FsbCA8LSBnZ3Bsb3QoZml0X2FsbCwgYWVzKHggPSBnZW9tX21lYW4vNjAsIHkgPSBkKSkgKwogIGZhY2V0X3dyYXAofiBwYXN0ZTAobl93aW5kb3dzLCAiIHdpbmRvdyhzKSAoIiwgd2luZG93X3R5cGUsICIpIikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjAxKSArCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IGZpdF9hbGxfYXZnLCBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmb3JtdWxhID0geSB+IHgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBmaXRfYWxsX2F2ZywgY29sb3VyID0gInJlZCIpICsKICBwbG90X3RpbWVzY2FsZXMoKSArCiAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWluKSIsIHkgPSAiRml0dGVkIHBhcmFtZXRlciIsIHRpdGxlID0gIkRlY2F5IGQiKQoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJkX2ZpdF9hbGwucG5nIiksIHBfZF9hbGwsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpCmBgYAoKIVtdKC4uL291dHB1dC9kX2ZpdF9hbGwucG5nKQoKYGBge3IgZml0dGVkLWh9CnBfaF9hbGwgPC0gZ2dwbG90KGZpdF9hbGwsIGFlcyh4ID0gZ2VvbV9tZWFuLzYwLCB5ID0gaCkpICsKICBmYWNldF93cmFwKH4gcGFzdGUwKG5fd2luZG93cywgIiB3aW5kb3cocykgKCIsIHdpbmRvd190eXBlLCAiKSIpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC4wMSkgKwogIGdlb21fc21vb3RoKGRhdGEgPSBmaXRfYWxsX2F2ZywgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgZm9ybXVsYSA9IHkgfiB4KSArCiAgZ2VvbV9wb2ludChkYXRhID0gZml0X2FsbF9hdmcsIGNvbG91ciA9ICJyZWQiKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWluKSIsIHkgPSAiRml0dGVkIHBhcmFtZXRlciIsIHRpdGxlID0gIlNjYWxpbmcgZmFjdG9yIGgiKQoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJoX2ZpdF9hbGwucG5nIiksIHBfaF9hbGwsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpCmBgYAoKIVtdKC4uL291dHB1dC9oX2ZpdF9hbGwucG5nKQoKYGBge3IgZml0dGVkLWgtZmlsdGVyZWR9CnBfaF9maWx0X2FsbCA8LSBnZ3Bsb3QoZml0X2FsbFtoID49IDFlLTE1XSwgYWVzKHggPSBnZW9tX21lYW4vNjAsIHkgPSBoKSkgKwogIGZhY2V0X3dyYXAofiBwYXN0ZTAobl93aW5kb3dzLCAiIHdpbmRvdyhzKSAoIiwgd2luZG93X3R5cGUsICIpIikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjAxKSArCiAgZ2VvbV9zbW9vdGgoZGF0YSA9IGZpdF9hbGxfYXZnLCBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmb3JtdWxhID0geSB+IHgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBmaXRfYWxsX2F2ZywgY29sb3VyID0gInJlZCIpICsKICBwbG90X3RpbWVzY2FsZXMoKSArCiAgc2NhbGVfeV9sb2cxMCgpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJGaXR0ZWQgcGFyYW1ldGVyIiwgdGl0bGUgPSAiU2NhbGluZyBmYWN0b3IgaCAoaCA+PSAxZS0xNSkiKQoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJoX2ZpdF9hbGxfZmlsdGVyZWQucG5nIiksIHBfaF9maWx0X2FsbCwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNikKYGBgCgohW10oLi4vb3V0cHV0L2hfZml0X2FsbF9maWx0ZXJlZC5wbmcpCgpDcmVhdGUgYSBwcmV0dHkgdmVyc2lvbiBvZiB0aGUgMjAtYmluIHBhcmFtZXRlciBwbG90cy4KYGBge3J9CnBsb3RfcGFyYW1ldGVyIDwtIGZ1bmN0aW9uKGRfcGFyYW1ldGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJfbmFtZSA9ICIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfeCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ195ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Bsb3QgPSBUUlVFKSB7CiAgCiAgIyBDYWxjdWxhdGUgUi1zcXVhcmVkCiAgeCA8LSBkX3BhcmFtZXRlcltuX3dpbmRvd3MgPT0gbl93LCBnZW9tX21lYW5dLzYwCiAgeSA8LSBkX3BhcmFtZXRlcltuX3dpbmRvd3MgPT0gbl93LCBwYXJhbWV0ZXJdCiAgaWYgKGxvZ194KSB4IDwtIGxvZyh4KQogIGlmIChsb2dfeSkgeSA8LSBsb2coeSkKICAKICBtIDwtIGxtKHkgfiB4KQoKICBlcSA8LSBzdWJzdGl0dXRlKHBhcmFtZXRlcl9uYW1lID09IGEtYiAlKiUgbG4oaXRhbGljKHQpKSwgCiAgICAgICAgICAgICAgICAgICBsaXN0KHBhcmFtZXRlcl9uYW1lID0gcGFyYW1ldGVyX25hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgIGEgPSBmb3JtYXQodW5uYW1lKGNvZWYobSlbMV0pLCBkaWdpdHMgPSAzKSwKICAgICAgICAgICAgICAgICAgICAgICAgYiA9IGZvcm1hdChhYnModW5uYW1lKGNvZWYobSlbMl0pKSwgZGlnaXRzID0gMykpKQogIAogIGVxIDwtIGFzLmNoYXJhY3Rlcihhcy5leHByZXNzaW9uKGVxKSkKICAKICByc3EgPC0gcGFzdGUoIlJeMiA9PSIsIHNjYWxlczo6bnVtYmVyKHN1bW1hcnkobSkkci5zcXVhcmVkLCBhY2N1cmFjeSA9IC4wMSkpCgogIHAgPC0gZ2dwbG90KCkgKwogICAgIyBXaW5kb3cgYmFja2dyb3VuZAogICAgZ2VvbV9yZWN0KGRhdGEgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IG5fd10sCiAgICAgICAgICAgICAgYWVzKHhtaW4gPSBzdGFydC82MCwgeG1heCA9IGlmZWxzZShpcy5uYShzaGlmdChzdGFydCwgLTEpKSwgZW5kLCBzaGlmdChzdGFydCwgLTEpKS82MCwKICAgICAgICAgICAgICAgICAgeW1pbiA9IGlmZWxzZShsb2dfeSwgMCwgLUluZiksIHltYXggPSBJbmYsIGFscGhhID0gYXMuZmFjdG9yKHdpbmRvdykpLAogICAgICAgICAgICAgIGZpbGwgPSB3aW5kb3dfY29sKSArCiAgICAjIFJlZ3Jlc3Npb24gbGluZQogICAgZ2VvbV9zbW9vdGgoZGF0YSA9IGRfcGFyYW1ldGVyW25fd2luZG93cyA9PSBuX3ddLCAKICAgICAgICAgICAgICAgIGFlcyh5ID0gcGFyYW1ldGVyLCB4ID0gZ2VvbV9tZWFuLzYwKSwgCiAgICAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHgsIAogICAgICAgICAgICAgICAgY29sb3VyID0gcHJlZF9jb2wsIGZpbGwgPSBwcmVkX2NvbCkgKwogICAgIyBQYXJhbWV0ZXIgdmFsdWVzCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkX3BhcmFtZXRlcltuX3dpbmRvd3MgPT0gbl93XSwKICAgICAgICAgICAgICAgYWVzKHkgPSBwYXJhbWV0ZXIsIHggPSBnZW9tX21lYW4vNjApKSArCiAgICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gcmVwKGMoLjEsIC4yNSksIGNlaWxpbmcobl93LzIpKSkgKwogICAgIyBSLXNxdWFyZWQKICAgIGdlb21fbGFiZWwoYWVzKHggPSBJbmYsIHkgPSBJbmYsIGxhYmVsID0gcnNxKSwKICAgICAgICAgICAgICBsYWJlbC5wYWRkaW5nID0gdW5pdCguNSwgImxpbmVzIiksCiAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IE5BLAogICAgICAgICAgICAgIGZpbGwgPSBOQSwKICAgICAgICAgICAgICBoanVzdCA9ICJpbndhcmQiLCB2anVzdCA9ICJpbndhcmQiLAogICAgICAgICAgICAgIHBhcnNlID0gVFJVRSkgKwogICAgZ2VvbV9sYWJlbChhZXMoeCA9IGlmZWxzZShsb2dfeCwgMCwgLUluZiksIHkgPSBpZmVsc2UobG9nX3ksIDAsIC1JbmYpLCBsYWJlbCA9IGVxKSwKICAgICAgICAgICAgICBsYWJlbC5wYWRkaW5nID0gdW5pdCguNSwgImxpbmVzIiksCiAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IE5BLAogICAgICAgICAgICAgIGZpbGwgPSBOQSwKICAgICAgICAgICAgICBoanVzdCA9ICJpbndhcmQiLCB2anVzdCA9ICJpbndhcmQiLAogICAgICAgICAgICAgIHBhcnNlID0gVFJVRSkgKwogICAgIyBQbG90IHNldHVwCiAgICBndWlkZXMoYWxwaGEgPSAibm9uZSIpICsKICAgIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbnV0ZXMpIiwKICAgICAgICAgeSA9ICJGaXR0ZWQgcGFyYW1ldGVyIikgKwogICAgc2NhbGVfeF9jb250aW51b3VzKHNlYy5heGlzID0gc2VjX2F4aXMofi54LCBicmVha3MgPSBsYWJlbF94LCBsYWJlbHMgPSBsYWJlbF90eHQpKSArCiAgICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMod2luZG93X3JhbmdlWzEsIHN0YXJ0XSwgd2luZG93X3JhbmdlWzEsIGVuZF0pLzYwLAogICAgICAgICAgICAgICAgICAgIHlsaW0gPSBjKG1pbih5KSAtIC4xKmRpZmYocmFuZ2UoeSkpLCBtYXgoeSkgKyAuMSpkaWZmKHJhbmdlKHkpKSksCiAgICAgICAgICAgICAgICAgICAgY2xpcCA9ICJvZmYiKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgKwogICAgdGhlbWUocGxvdC5tYXJnaW4gPSBtYXJnaW4oNywgMTQsIDcsIDcpLAogICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54LnRvcCA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4oYiA9IDgpKSkKICAKICAKICAjIFRyYW5zZm9ybSBzY2FsZXMgaWYgcmVxdWlyZWQKICBpZiAobG9nX3gpIHsKICAgIHAgPC0gcCArCiAgICAgIHNjYWxlX3hfbG9nMTAoCiAgICAgICAgYnJlYWtzID0gc2NhbGVzOjp0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCksCiAgICAgICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIiwgc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpLAogICAgICAgIGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgc2VjLmF4aXMgPSBzZWNfYXhpcyh+LngsIGJyZWFrcyA9IGxhYmVsX3gsIGxhYmVscyA9IGxhYmVsX3R4dCkKICAgICAgKSArCiAgICAgIGFubm90YXRpb25fbG9ndGlja3Moc2lkZXMgPSAiYiIsIG91dHNpZGUgPSBUKSArCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gOCkpKQogIH0KICAKICBpZiAobG9nX3kpIHsKICAgIHAgPC0gcCArCiAgICAgIHNjYWxlX3lfbG9nMTAoKSArCiAgICAgIGFubm90YXRpb25fbG9ndGlja3Moc2lkZXMgPSAibCIsIG91dHNpZGUgPSBUKSArCiAgICAgIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYyh3aW5kb3dfcmFuZ2VbMSwgc3RhcnRdLCB3aW5kb3dfcmFuZ2VbMSwgZW5kXSkvNjAsCiAgICAgICAgICAgICAgICAgICAgICB5bGltID0gYygxZS0zLCAxLjI1KSwKICAgICAgICAgICAgICAgICAgICAgIGNsaXAgPSAib2ZmIikgKwogICAgICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4ociA9IDgpKSkKICB9CgogIGlmIChwcmludF9wbG90KSBwcmludChwKQogIHJldHVybiAocCkKCn0KYGBgCgpgYGB7cn0KZml0X3RhdV9hdmcgPC0gY29weShmaXRfYWxsX2F2ZykKc2V0bmFtZXMoZml0X3RhdV9hdmcsICJ0YXUiLCAicGFyYW1ldGVyIikKCnBfdGF1X3RpbWUgPC0gcGxvdF9wYXJhbWV0ZXIoZF9wYXJhbWV0ZXIgPSBmaXRfdGF1X2F2Z1t3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcl9uYW1lID0gcXVvdGUodGF1KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAyMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9wbG90ID0gRkFMU0UpCgpmaXRfZF9hdmcgPC0gY29weShmaXRfYWxsX2F2ZykKc2V0bmFtZXMoZml0X2RfYXZnLCAiZCIsICJwYXJhbWV0ZXIiKQoKcF9kX3RpbWUgPC0gcGxvdF9wYXJhbWV0ZXIoZF9wYXJhbWV0ZXIgPSBmaXRfZF9hdmdbd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW1ldGVyX25hbWUgPSBxdW90ZShpdGFsaWMoZCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAyMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfcGxvdCA9IEZBTFNFKQoKZml0X2hfYXZnIDwtIGNvcHkoZml0X2FsbF9hdmcpCnNldG5hbWVzKGZpdF9oX2F2ZywgImgiLCAicGFyYW1ldGVyIikKCnBfaF90aW1lIDwtIHBsb3RfcGFyYW1ldGVyKGRfcGFyYW1ldGVyID0gZml0X2hfYXZnW3dpbmRvd190eXBlID09ICJyZWd1bGFyIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtZXRlcl9uYW1lID0gcXVvdGUobG4oaXRhbGljKGgpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ195ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Bsb3QgPSBGQUxTRSkKYGBgCgoKIyMjIEZpdCBieSBsZWFybmVyCgpUaGUgZm9sbG93aW5nIHBsb3Qgc2hvdyB0aGUgcGFyYW1ldGVycyB0aGF0IHdlcmUgZml0dGVkIHBlciBsZWFybmVyIChzcGVjaWZpY2FsbHk6IHRoZSBsaW5lcyBhcmUgbGluZWFyIG1vZGVscyBmaXR0ZWQgdG8gdGhlIGJpbndpc2UgcGFyYW1ldGVyIGVzdGltYXRlcyBzaG93biBhcyBwb2ludHMpLgpJdCBpcyBjbGVhciB0aGF0IG5lYXJseSBhbGwgbGVhcm5lcnMgZXhoaWJpdCB0aGUgcGF0dGVybiB0aGF0IGlzIGFsc28gZm91bmQgYXQgdGhlIGdyb3VwLWxldmVsOiBhcyB0aGUgYmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIGluY3JlYXNlcywgdGF1L2QvaCBkZWNyZWFzZXMuCkluIGFkZGl0aW9uLCB0aGVyZSBzZWVtIHRvIGJlIGluZGl2aWR1YWwgZGlmZmVyZW5jZXMgaW4gYm90aCBpbnRlcmNlcHQgYW5kIHNsb3BlLCB3aGljaCBjb3VsZCBpbmRpY2F0ZSB0aGF0IGEgbGVhcm5lci1zcGVjaWZpYyBmaXQgY291bGQgcHJvZHVjZSBiZXR0ZXIgcmVzdWx0cyB0aGFuIGEgZ3JvdXAtbGV2ZWwgZml0LgoKYGBge3IgZml0dGVkLXRhdS1ieS1sZWFybmVyfQpmaXRfYnlfbGVhcm5lcl9hdmcgPC0gZml0X2J5X2xlYXJuZXJbLCAgLiguTiwgdGF1ID0gbWVhbih0YXUpLCBkID0gbWVhbihkKSwgaCA9IG1lYW4oaCkpLCAgYnkgPSAuKG5fd2luZG93cywgd2luZG93X3R5cGUsIHdpbmRvdywgZ2VvbV9tZWFuLCBzdWJfbGFiZWwpXQoKcF90YXVfbGVhcm5lciA8LSBnZ3Bsb3QoZml0X2J5X2xlYXJuZXJfYXZnLCBhZXMoeCA9IGdlb21fbWVhbi82MCwgeSA9IHRhdSwgY29sb3VyID0gc3ViX2xhYmVsKSkgKwogIGZhY2V0X3dyYXAofiBwYXN0ZTAobl93aW5kb3dzLCAiIHdpbmRvdyhzKSAoIiwgd2luZG93X3R5cGUsICIpIikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjAxKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgZm9ybXVsYSA9IHkgfiB4LCBsaW5ld2lkdGggPSAuMSkgKwogIHBsb3RfdGltZXNjYWxlcygpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtNywgMCkpICsKICBndWlkZXMoY29sb3VyID0gIm5vbmUiKSArCiAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWluKSIsIHkgPSAiRml0dGVkIHBhcmFtZXRlciIsIHRpdGxlID0gIlJldHJpZXZhbCB0aHJlc2hvbGQgdGF1IGJ5IGxlYXJuZXIiKQoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJ0YXVfZml0X2J5X2xlYXJuZXIucG5nIiksIHBfdGF1X2xlYXJuZXIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpCmBgYAoKIVtdKC4uL291dHB1dC90YXVfZml0X2J5X2xlYXJuZXIucG5nKQoKYGBge3IgZml0dGVkLWQtYnktbGVhcm5lcn0KcF9kX2xlYXJuZXIgPC0gZ2dwbG90KGZpdF9ieV9sZWFybmVyX2F2ZywgYWVzKHggPSBnZW9tX21lYW4vNjAsIHkgPSBkLCBjb2xvdXIgPSBzdWJfbGFiZWwpKSArCiAgZmFjZXRfd3JhcCh+IHBhc3RlMChuX3dpbmRvd3MsICIgd2luZG93KHMpICgiLCB3aW5kb3dfdHlwZSwgIikiKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMDEpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmb3JtdWxhID0geSB+IHgsIGxpbmV3aWR0aCA9IC4xKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJGaXR0ZWQgcGFyYW1ldGVyIiwgdGl0bGUgPSAiRGVjYXkgZCBieSBsZWFybmVyIikKCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAiZF9maXRfYnlfbGVhcm5lci5wbmciKSwgcF9kX2xlYXJuZXIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpCmBgYAoKIVtdKC4uL291dHB1dC9kX2ZpdF9ieV9sZWFybmVyLnBuZykKCmBgYHtyIGZpdHRlZC1oLWJ5LWxlYXJuZXJ9CnBfaF9sZWFybmVyIDwtIGdncGxvdChmaXRfYnlfbGVhcm5lcl9hdmcsIGFlcyh4ID0gZ2VvbV9tZWFuLzYwLCB5ID0gaCwgY29sb3VyID0gc3ViX2xhYmVsKSkgKwogIGZhY2V0X3dyYXAofiBwYXN0ZTAobl93aW5kb3dzLCAiIHdpbmRvdyhzKSAoIiwgd2luZG93X3R5cGUsICIpIikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjAxKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgZm9ybXVsYSA9IHkgfiB4LCBsaW5ld2lkdGggPSAuMSkgKwogIHBsb3RfdGltZXNjYWxlcygpICsKICBzY2FsZV95X2xvZzEwKCkgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJGaXR0ZWQgcGFyYW1ldGVyIiwgdGl0bGUgPSAiU2NhbGluZyBmYWN0b3IgaCBieSBsZWFybmVyIikKCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAiaF9maXRfYnlfbGVhcm5lci5wbmciKSwgcF9oX2xlYXJuZXIsIHdpZHRoID0gMTAsIGhlaWdodCA9IDYpCmBgYAoKIVtdKC4uL291dHB1dC9oX2ZpdF9ieV9sZWFybmVyLnBuZykKClRoZSBwbG90cyBiZWxvdyBzaG93IHRoZSBjb2VmZmljaWVudHMgZnJvbSB0aGUgZml0dGVkIGxpbmVhciBtb2RlbHMgcGVyIGxlYXJuZXIuCkhlcmUgd2UgYWxzbyBzZWUgdGhhdCBtb3N0IGxlYXJuZXJzIGFyZSBjbHVzdGVyZWQgYXJvdW5kIHNpbWlsYXIgdmFsdWVzLgoKYGBge3J9CnRhdV9ieV9sZWFybmVyX2xtIDwtIGxtX2ZpdFtzdWJzZXQgPT0gImJ5X2xlYXJuZXIiLCAuKG1hcF9kZnIobG1fdGF1LCBmdW5jdGlvbiAobSkgewogIGxpc3QoaW50ZXJjZXB0ID0gY29lZihtKVtbMV1dLAogICAgICAgc2xvcGUgPSBjb2VmKG0pW1syXV0pCn0pKV0KCnBfdGF1X2xlYXJuZXJfbG0gPC0gZ2dwbG90KHRhdV9ieV9sZWFybmVyX2xtLCBhZXMoeCA9IGludGVyY2VwdCwgeSA9IHNsb3BlKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMjUpICsKICBsYWJzKHggPSAiSW50ZXJjZXB0IiwgeSA9ICJTbG9wZSIsIHRpdGxlID0gIlJldHJpZXZhbCB0aHJlc2hvbGQgdGF1IGJ5IGxlYXJuZXIiKQoKZF9ieV9sZWFybmVyX2xtIDwtIGxtX2ZpdFtzdWJzZXQgPT0gImJ5X2xlYXJuZXIiLCAuKG1hcF9kZnIobG1fZCwgZnVuY3Rpb24gKG0pIHsKICBsaXN0KGludGVyY2VwdCA9IGNvZWYobSlbWzFdXSwKICAgICAgIHNsb3BlID0gY29lZihtKVtbMl1dKQp9KSldCgpwX2RfbGVhcm5lcl9sbSA8LSBnZ3Bsb3QoZF9ieV9sZWFybmVyX2xtLCBhZXMoeCA9IGludGVyY2VwdCwgeSA9IHNsb3BlKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMjUpICsKICBsYWJzKHggPSAiSW50ZXJjZXB0IiwgeSA9ICJTbG9wZSIsIHRpdGxlID0gIkRlY2F5IGQgYnkgbGVhcm5lciIpCgoKaF9ieV9sZWFybmVyX2xtIDwtIGxtX2ZpdFtzdWJzZXQgPT0gImJ5X2xlYXJuZXIiLCAuKG1hcF9kZnIobG1faCwgZnVuY3Rpb24gKG0pIHsKICBsaXN0KGludGVyY2VwdCA9IGNvZWYobSlbWzFdXSwKICAgICAgIHNsb3BlID0gY29lZihtKVtbMl1dKQp9KSldCgpwX2hfbGVhcm5lcl9sbSA8LSBnZ3Bsb3QoaF9ieV9sZWFybmVyX2xtLCBhZXMoeCA9IGludGVyY2VwdCwgeSA9IHNsb3BlKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMjUpICsKICBsYWJzKHggPSAiSW50ZXJjZXB0IiwgeSA9ICJTbG9wZSIsIHRpdGxlID0gIlNjYWxpbmcgZmFjdG9yIGggYnkgbGVhcm5lciIpCgpnZ3NhdmUoaGVyZSgib3V0cHV0IiwgInRhdV9maXRfYnlfbGVhcm5lcl9sbS5wbmciKSwgcF90YXVfbGVhcm5lcl9sbSwgd2lkdGggPSA1LCBoZWlnaHQgPSA0KQpnZ3NhdmUoaGVyZSgib3V0cHV0IiwgImRfZml0X2J5X2xlYXJuZXJfbG0ucG5nIiksIHBfZF9sZWFybmVyX2xtLCB3aWR0aCA9IDUsIGhlaWdodCA9IDQpCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAiaF9maXRfYnlfbGVhcm5lcl9sbS5wbmciKSwgcF9oX2xlYXJuZXJfbG0sIHdpZHRoID0gNSwgaGVpZ2h0ID0gNCkKYGBgCgohW10oLi4vb3V0cHV0L3RhdV9maXRfYnlfbGVhcm5lcl9sbS5wbmcpCiFbXSguLi9vdXRwdXQvZF9maXRfYnlfbGVhcm5lcl9sbS5wbmcpCiFbXSguLi9vdXRwdXQvaF9maXRfYnlfbGVhcm5lcl9sbS5wbmcpCgoKIyMjIEZpdCBieSBhbW91bnQgb2YgcHJhY3RpY2UKVGhlIHBsb3RzIGJlbG93IHNob3cgdGhlIGZpdHRlZCBwYXJhbWV0ZXJzIGJ5IGFtb3VudCBvZiBwcmFjdGljZS4KCk5vdGljZSB0aGF0IHRoZXJlIHNlZW1zIHRvIGJlIGEgc3lzdGVtYXRpYyBlZmZlY3Qgb2YgdGhlIGFtb3VudCBvZiBwcmFjdGljZSBvbiB0aGUgZml0dGVkIG1vZGVsIHBhcmFtZXRlcnM6IG1vcmUgcHJhY3RpY2UgaW4gdGhlIGZpcnN0IHNlc3Npb24gY29ycmVzcG9uZHMgdG8gYSBoaWdoZXIgcmV0cmlldmFsIHRocmVzaG9sZCAvIChpbml0aWFsKSBkZWNheSAvIHNjYWxpbmcgZmFjdG9yLgpBIGxvZ2ljYWwgZXhwbGFuYXRpb24gZm9yIHRoaXMgY291bGQgYmUgdGhhdCB0aGUgYW1vdW50IG9mIHByYWN0aWNlIGlzIGNvcnJlbGF0ZWQgd2l0aCBpdGVtIGRpZmZpY3VsdHk6IGZvciBpbnN0YW5jZSBtb3JlIGRpZmZpY3VsdCBpdGVtcyBhcmUgc2VsZWN0ZWQgbW9yZSBmcmVxdWVudGx5IGJ5IHRoZSBhZGFwdGl2ZSBsZWFybmluZyBzeXN0ZW0sIGFuZCB0aGUgZXh0cmEgcHJhY3RpY2Ugb2YgdGhlc2UgaXRlbXMgbGVhZHMgdG8gaGlnaGVyIHByZWRpY3RlZCBhY3RpdmF0aW9uIGluIHNlc3Npb24gMiAoYmVjYXVzZSBvZiBhZGRpdGlvbmFsIHRyYWNlcyksIGJ1dCB0aGVpciBkaWZmaWN1bHR5IG1lYW5zIHRoYXQgYWNjdXJhY3kgaXMgbm90IGFjdHVhbGx5IGhpZ2hlciwgd2hpY2ggbWVhbnMgdGhhdCBhIHN0cm9uZ2VyIGRlY2F5IGlzIG5lY2Vzc2FyeSB0byBicmlkZ2UgdGhlIGdhcC4KCmBgYHtyIGZpdHRlZC10YXUtYnktcHJhY3RpY2V9CmZpdF9ieV9wcmFjdGljZV9hdmcgPC0gZml0X2J5X3ByYWN0aWNlWywgIC4oLk4sIHRhdSA9IG1lYW4odGF1KSwgZCA9IG1lYW4oZCksIGggPSBtZWFuKGgpKSwgIGJ5ID0gLihuX3dpbmRvd3MsIHdpbmRvd190eXBlLCB3aW5kb3csIGdlb21fbWVhbiwgc3ViX2xhYmVsKV0KCnBfdGF1X3ByYWN0aWNlIDwtIGdncGxvdChmaXRfYnlfcHJhY3RpY2VfYXZnLCBhZXMoeCA9IGdlb21fbWVhbi82MCwgeSA9IHRhdSwgY29sb3VyID0gc3ViX2xhYmVsKSkgKwogIGZhY2V0X3dyYXAofiBwYXN0ZTAobl93aW5kb3dzLCAiIHdpbmRvdyhzKSAoIiwgd2luZG93X3R5cGUsICIpIikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjAxKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgZm9ybXVsYSA9IHkgfiB4LCBsaW5ld2lkdGggPSAuMSkgKwogIHBsb3RfdGltZXNjYWxlcygpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtNywgMCkpICsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19kKCkgKwogIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbikiLCB5ID0gIkZpdHRlZCBwYXJhbWV0ZXIiLCBjb2xvdXIgPSAiVHJpYWxzIiwgdGl0bGUgPSAiUmV0cmlldmFsIHRocmVzaG9sZCB0YXUgYnkgcHJhY3RpY2UiKQoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJ0YXVfZml0X2J5X3ByYWN0aWNlLnBuZyIpLCBwX3RhdV9wcmFjdGljZSwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNikKYGBgCgohW10oLi4vb3V0cHV0L3RhdV9maXRfYnlfcHJhY3RpY2UucG5nKQoKYGBge3IgZml0dGVkLWQtYnktcHJhY3RpY2V9CnBfZF9wcmFjdGljZSA8LSBnZ3Bsb3QoZml0X2J5X3ByYWN0aWNlX2F2ZywgYWVzKHggPSBnZW9tX21lYW4vNjAsIHkgPSBkLCBjb2xvdXIgPSBzdWJfbGFiZWwpKSArCiAgZmFjZXRfd3JhcCh+IHBhc3RlMChuX3dpbmRvd3MsICIgd2luZG93KHMpICgiLCB3aW5kb3dfdHlwZSwgIikiKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMDEpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBmb3JtdWxhID0geSB+IHgsIGxpbmV3aWR0aCA9IC4xKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoKSArCiAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWluKSIsIHkgPSAiRml0dGVkIHBhcmFtZXRlciIsIGNvbG91ciA9ICJUcmlhbHMiLCB0aXRsZSA9ICJEZWNheSBkIGJ5IHByYWN0aWNlIikKCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAiZF9maXRfYnlfcHJhY3RpY2UucG5nIiksIHBfZF9wcmFjdGljZSwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNikKYGBgCgohW10oLi4vb3V0cHV0L2RfZml0X2J5X3ByYWN0aWNlLnBuZykKCmBgYHtyIGZpdHRlZC1oLWJ5LXByYWN0aWNlfQpwX2hfcHJhY3RpY2UgPC0gZ2dwbG90KGZpdF9ieV9wcmFjdGljZV9hdmcsIGFlcyh4ID0gZ2VvbV9tZWFuLzYwLCB5ID0gaCwgY29sb3VyID0gc3ViX2xhYmVsKSkgKwogIGZhY2V0X3dyYXAofiBwYXN0ZTAobl93aW5kb3dzLCAiIHdpbmRvdyhzKSAoIiwgd2luZG93X3R5cGUsICIpIikpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjAxKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgZm9ybXVsYSA9IHkgfiB4LCBsaW5ld2lkdGggPSAuMSkgKwogIHBsb3RfdGltZXNjYWxlcygpICsKICBzY2FsZV95X2xvZzEwKCkgKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2QoKSArCiAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWluKSIsIHkgPSAiRml0dGVkIHBhcmFtZXRlciIsIGNvbG91ciA9ICJUcmlhbHMiLCB0aXRsZSA9ICJTY2FsaW5nIGZhY3RvciBoIGJ5IHByYWN0aWNlIikKCmdnc2F2ZShoZXJlKCJvdXRwdXQiLCAiaF9maXRfYnlfcHJhY3RpY2UucG5nIiksIHBfaF9wcmFjdGljZSwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gNikKYGBgCgohW10oLi4vb3V0cHV0L2hfZml0X2J5X3ByYWN0aWNlLnBuZykKClRoZSBwbG90cyBiZWxvdyBzaG93IHRoZSBjb2VmZmljaWVudHMgZnJvbSB0aGUgZml0dGVkIGxpbmVhciBtb2RlbHMgcGVyIGFtb3VudCBvZiBwcmFjdGljZS4KYGBge3J9CnRhdV9ieV9wcmFjdGljZV9sbSA8LSBsbV9maXRbc3Vic2V0ID09ICJieV9wcmFjdGljZSIsIC4obWFwX2RmcihsbV90YXUsIGZ1bmN0aW9uIChtKSB7CiAgbGlzdChpbnRlcmNlcHQgPSBjb2VmKG0pW1sxXV0sCiAgICAgICBzbG9wZSA9IGNvZWYobSlbWzJdXSkKfSkpXQoKcF90YXVfcHJhY3RpY2VfbG0gPC0gZ2dwbG90KHRhdV9ieV9wcmFjdGljZV9sbSwgYWVzKHggPSBpbnRlcmNlcHQsIHkgPSBzbG9wZSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjI1KSArCiAgbGFicyh4ID0gIkludGVyY2VwdCIsIHkgPSAiU2xvcGUiLCB0aXRsZSA9ICJSZXRyaWV2YWwgdGhyZXNob2xkIHRhdSBieSBwcmFjdGljZSIpCgpkX2J5X3ByYWN0aWNlX2xtIDwtIGxtX2ZpdFtzdWJzZXQgPT0gImJ5X3ByYWN0aWNlIiwgLihtYXBfZGZyKGxtX2QsIGZ1bmN0aW9uIChtKSB7CiAgbGlzdChpbnRlcmNlcHQgPSBjb2VmKG0pW1sxXV0sCiAgICAgICBzbG9wZSA9IGNvZWYobSlbWzJdXSkKfSkpXQoKcF9kX3ByYWN0aWNlX2xtIDwtIGdncGxvdChkX2J5X3ByYWN0aWNlX2xtLCBhZXMoeCA9IGludGVyY2VwdCwgeSA9IHNsb3BlKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMjUpICsKICBsYWJzKHggPSAiSW50ZXJjZXB0IiwgeSA9ICJTbG9wZSIsIHRpdGxlID0gIkRlY2F5IGQgYnkgcHJhY3RpY2UiKQoKCmhfYnlfcHJhY3RpY2VfbG0gPC0gbG1fZml0W3N1YnNldCA9PSAiYnlfcHJhY3RpY2UiLCAuKG1hcF9kZnIobG1faCwgZnVuY3Rpb24gKG0pIHsKICBsaXN0KGludGVyY2VwdCA9IGNvZWYobSlbWzFdXSwKICAgICAgIHNsb3BlID0gY29lZihtKVtbMl1dKQp9KSldCgpwX2hfcHJhY3RpY2VfbG0gPC0gZ2dwbG90KGhfYnlfcHJhY3RpY2VfbG0sIGFlcyh4ID0gaW50ZXJjZXB0LCB5ID0gc2xvcGUpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC4yNSkgKwogIGxhYnMoeCA9ICJJbnRlcmNlcHQiLCB5ID0gIlNsb3BlIiwgdGl0bGUgPSAiU2NhbGluZyBmYWN0b3IgaCBieSBwcmFjdGljZSIpCgoKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJ0YXVfZml0X2J5X3ByYWN0aWNlX2xtLnBuZyIpLCBwX3RhdV9wcmFjdGljZV9sbSwgd2lkdGggPSA1LCBoZWlnaHQgPSA0KQpnZ3NhdmUoaGVyZSgib3V0cHV0IiwgImRfZml0X2J5X3ByYWN0aWNlX2xtLnBuZyIpLCBwX2RfcHJhY3RpY2VfbG0sIHdpZHRoID0gNSwgaGVpZ2h0ID0gNCkKZ2dzYXZlKGhlcmUoIm91dHB1dCIsICJoX2ZpdF9ieV9wcmFjdGljZV9sbS5wbmciKSwgcF9oX3ByYWN0aWNlX2xtLCB3aWR0aCA9IDUsIGhlaWdodCA9IDQpCmBgYAoKIVtdKC4uL291dHB1dC90YXVfZml0X2J5X3ByYWN0aWNlX2xtLnBuZykKIVtdKC4uL291dHB1dC9kX2ZpdF9ieV9wcmFjdGljZV9sbS5wbmcpCiFbXSguLi9vdXRwdXQvaF9maXRfYnlfcHJhY3RpY2VfbG0ucG5nKQoKCiMgRXZhbHVhdGUgbW9kZWxzCgojIyBQcmVkaWN0IHJlY2FsbAoKRGVmaW5lIHRoZSBmdW5jdGlvbiB0byBwcmVkaWN0IHJlY2FsbCBmcm9tIGZpdHRlZCBwYXJhbWV0ZXJzOgpgYGB7ciBwcmVkaWN0LXJlY2FsbH0KcHJlZGljdF9yZWNhbGwgPC0gZnVuY3Rpb24gKG1vZGVsX2ZpdCkgewogIAogIGRfZml0IDwtIHN3aXRjaChtb2RlbF9maXRbMSwgc3Vic2V0XSwKICAgICAgICAgICAgICAgICAgImFsbCIgPSBsaXN0KGNvcHkoZF9sYXN0KSksCiAgICAgICAgICAgICAgICAgICJieV9sZWFybmVyIiA9IGNvcHkoZF9sYXN0X2J5X2xlYXJuZXIpLAogICAgICAgICAgICAgICAgICAiYnlfcHJhY3RpY2UiID0gY29weShkX2xhc3RfYnlfcHJhY3RpY2UpKQogIAogIGZpdF9ieV93aW5kb3cgPC0gc3BsaXQobW9kZWxfZml0LCBieSA9ICJ3aW5kb3dfaWQiKQogIAogIGZ1dHVyZV9tYXAoZml0X2J5X3dpbmRvdywgZnVuY3Rpb24gKGZpdF93aW5kb3cpIHsKICAgIAogICAgZXh0cmFwb2xhdGVfb3V0c2lkZV9maXR0ZWRfd2luZG93IDwtIGZpdF93aW5kb3dbMSwgc3Vic2V0ID09ICJhbGwiICYmIHdpbmRvd190eXBlICVpbiUgYygic2hvcnQiLCAiMjRoIildCiAgICAKICAgIGlmIChleHRyYXBvbGF0ZV9vdXRzaWRlX2ZpdHRlZF93aW5kb3cpIHsKICAgICAgIyBJbiB0aGVzZSBjYXNlcywgYXBwbHkgdGhlIHBhcmFtZXRlcnMgb3V0c2lkZSB0aGUgZml0dGVkIHdpbmRvdwogICAgICBkX3dpbmRvdyA8LSBkX2ZpdFtbMV1dCiAgICAgIGRfd2luZG93Wywgd2luZG93IDo9IDFdCiAgICAgIGRfd2luZG93Wywgc2VxdWVuY2UgOj0gMTouTl0KICAgICAgZF93aW5kb3cgPC0gZF9mW2Rfd2luZG93WywgLihpZCwgc2VxdWVuY2UsIHdpbmRvdyldLCBvbiA9ICJpZCJdCiAgICAgIAogICAgfSBlbHNlIHsKICAgICAgIyBPdGhlcndpc2UsIHVzZSBmaXR0ZWQgcGFyYW1ldGVycyBvbmx5IGluIHRoZWlyIG93biB3aW5kb3cKICAgICAgZml0X3dpbmRvd1ssIHNlcXVlbmNlIDo9IDE6Lk5dCiAgICAgIGRfd2luZG93IDwtIGRfZltmaXRfd2luZG93WywgLihpZCwgc2VxdWVuY2UsIHdpbmRvdyldLCBvbiA9ICJpZCJdCiAgICB9CiAgICAKICAgIGRfd2luZG93X3NlcXMgPC0gZ2VuZXJhdGVfc2VxX2xpc3QoZF93aW5kb3cpCiAgICAKICAgIGNvcnJlY3QgPC0gbWFwX2ludChkX3dpbmRvd19zZXFzLCB+LiRjb3JyZWN0KQogICAgdGltZV9iZXR3ZWVuIDwtIG1hcF9kYmwoZF93aW5kb3dfc2Vxcywgfi4kdGltZV9iZXR3ZWVuKQogICAgCiAgICBpZiAoZXh0cmFwb2xhdGVfb3V0c2lkZV9maXR0ZWRfd2luZG93KSB7CiAgICAgICAgZml0dGVkX3RhdSA8LSBmaXRfd2luZG93WzEsIHRhdV0KICAgICAgICBmaXR0ZWRfZCA8LSBmaXRfd2luZG93WywgbWVkaWFuKGQpXQogICAgICAgIGZpdHRlZF9oIDwtIGZpdF93aW5kb3dbLCBtZWRpYW4oaCldCiAgICB9IGVsc2UgewogICAgICAgIGZpdHRlZF90YXUgPC0gZml0X3dpbmRvdyR0YXUKICAgICAgICBmaXR0ZWRfZCA8LSBmaXRfd2luZG93JGQKICAgICAgICBmaXR0ZWRfaCA8LSBmaXRfd2luZG93JGgKICAgIH0KICAgIAogICAgIyBQcmVkaWN0aW9uIGZyb20gZml0dGVkIHRhdQogICAgYWMgPC0gbWFwX2RibChkX3dpbmRvd19zZXFzLCBmdW5jdGlvbiAoeCkgewogICAgICBhY3RpdmF0aW9uKHgkdGltZV93aXRoaW4sIHgkdGltZV9iZXR3ZWVuLCBtb2RlbF9wYXJhbXMkaCwgbW9kZWxfcGFyYW1zJGRlY2F5KQogICAgfSkKICAgIHBfcmVjYWxsX3RhdSA8LSBwX3JlY2FsbChhYywgZml0dGVkX3RhdSwgbW9kZWxfcGFyYW1zJHMpCiAgICAKICAgICMgUHJlZGljdGlvbiBmcm9tIGZpdHRlZCBkCiAgICBhY19kIDwtIG1hcDJfZGJsKGRfd2luZG93X3NlcXMsIGZpdHRlZF9kLCBmdW5jdGlvbiAoeCwgZCkgewogICAgICBhY3RpdmF0aW9uKHgkdGltZV93aXRoaW4sIHgkdGltZV9iZXR3ZWVuLCBtb2RlbF9wYXJhbXMkaCwgZCkKICAgIH0pCiAgICBwX3JlY2FsbF9kIDwtIHBfcmVjYWxsKGFjX2QsIG1vZGVsX3BhcmFtcyR0YXUsIG1vZGVsX3BhcmFtcyRzKQogICAgCiAgICAjIFByZWRpY3Rpb24gZnJvbSBmaXR0ZWQgaAogICAgYWNfaCA8LSBtYXAyX2RibChkX3dpbmRvd19zZXFzLCBmaXR0ZWRfaCwgZnVuY3Rpb24gKHgsIGgpIHsKICAgICAgYWN0aXZhdGlvbih4JHRpbWVfd2l0aGluLCB4JHRpbWVfYmV0d2VlbiwgaCwgbW9kZWxfcGFyYW1zJGRlY2F5KQogICAgfSkKICAgIHBfcmVjYWxsX2ggPC0gcF9yZWNhbGwoYWNfaCwgbW9kZWxfcGFyYW1zJHRhdSwgbW9kZWxfcGFyYW1zJHMpCiAgICAKICAgIGZpdF9pbmZvIDwtIGZpdF93aW5kb3dbLCAuKHN1YnNldCwgc3ViX2xhYmVsLCB3aW5kb3dfaWQsIG5fd2luZG93cywgd2luZG93LCBnZW9tX21lYW4sIHdpbmRvd190eXBlLCBpZCldCiAgICAKICAgIGlmIChleHRyYXBvbGF0ZV9vdXRzaWRlX2ZpdHRlZF93aW5kb3cpIHsKICAgICAgZml0X2luZm9bLCBpZCA6PSBOVUxMXQogICAgICBmaXRfaW5mbyA8LSBjYmluZChmaXRfaW5mb1sxXSwgZGF0YS50YWJsZShpZCA9IG1hcF9jaHIoZF93aW5kb3dfc2Vxcywgfi4kaWQpKSkKICAgIH0KICAgIAogICAgZGF0YS50YWJsZShmaXRfaW5mbywKICAgICAgICAgICAgICAgdGltZV9iZXR3ZWVuID0gdGltZV9iZXR3ZWVuLAogICAgICAgICAgICAgICBjb3JyZWN0ID0gY29ycmVjdCwKICAgICAgICAgICAgICAgcF9yZWNhbGxfdGF1ID0gcF9yZWNhbGxfdGF1LAogICAgICAgICAgICAgICBwX3JlY2FsbF9kID0gcF9yZWNhbGxfZCwKICAgICAgICAgICAgICAgcF9yZWNhbGxfaCA9IHBfcmVjYWxsX2gpCiAgfSkgfD4KICAgIHJiaW5kbGlzdCgpCn0KYGBgCgpQcmVkaWN0IHJlY2FsbCBmb3IgYWxsIGZpdHRlZCBtb2RlbHMgKG5vdGU6IHRoaXMgY2FuIHRha2UgYSB3aGlsZSEpLgooU2V0IGB1c2Vfc2F2ZWRfcHJlZGljdGlvbnNgIHRvIFRSVUUgdG8gdHJ5IHRvIGxvYWQgcHJldmlvdXMgcHJlZGljdGlvbnMgZnJvbSBmaWxlLikKYGBge3IgcHJlZGljdH0KdXNlX3NhdmVkX3ByZWRpY3Rpb25zIDwtIFRSVUUKCnByZWRfYWxsX3BhdGggPC0gaGVyZSgiZGF0YSIsICJwcmVkX2FsbC5jc3YiKQpwcmVkX2J5X2xlYXJuZXJfcGF0aCA8LSBoZXJlKCJkYXRhIiwgInByZWRfYnlfbGVhcm5lci5jc3YiKQpwcmVkX2J5X3ByYWN0aWNlX3BhdGggPC0gaGVyZSgiZGF0YSIsICJwcmVkX2J5X3ByYWN0aWNlLmNzdiIpCgppZiAoIXVzZV9zYXZlZF9wcmVkaWN0aW9ucyB8ICFmaWxlLmV4aXN0cyhwcmVkX2FsbF9wYXRoKSkgewogIHByZWRfYWxsIDwtIHByZWRpY3RfcmVjYWxsKGZpdF9hbGwpCiAgZndyaXRlKHByZWRfYWxsLCBwcmVkX2FsbF9wYXRoKQp9IGVsc2UgewogIHByZWRfYWxsIDwtIGZyZWFkKHByZWRfYWxsX3BhdGgpCn0KCmlmICghdXNlX3NhdmVkX3ByZWRpY3Rpb25zIHwgIWZpbGUuZXhpc3RzKHByZWRfYnlfbGVhcm5lcl9wYXRoKSkgewogIHByZWRfYnlfbGVhcm5lciA8LSBwcmVkaWN0X3JlY2FsbChmaXRfYnlfbGVhcm5lcikKICBmd3JpdGUocHJlZF9ieV9sZWFybmVyLCBwcmVkX2J5X2xlYXJuZXJfcGF0aCkKfSBlbHNlIHsKICBwcmVkX2J5X2xlYXJuZXIgPC0gZnJlYWQocHJlZF9ieV9sZWFybmVyX3BhdGgpCn0KCmlmICghdXNlX3NhdmVkX3ByZWRpY3Rpb25zIHwgIWZpbGUuZXhpc3RzKHByZWRfYnlfcHJhY3RpY2VfcGF0aCkpIHsKICBwcmVkX2J5X3ByYWN0aWNlIDwtIHByZWRpY3RfcmVjYWxsKGZpdF9ieV9wcmFjdGljZSkKICBmd3JpdGUocHJlZF9ieV9wcmFjdGljZSwgcHJlZF9ieV9wcmFjdGljZV9wYXRoKQp9IGVsc2UgewogIHByZWRfYnlfcHJhY3RpY2UgPC0gZnJlYWQocHJlZF9ieV9wcmFjdGljZV9wYXRoKQp9CgpwcmVkcyA8LSByYmluZChwcmVkX2FsbCwgcHJlZF9ieV9sZWFybmVyLCBwcmVkX2J5X3ByYWN0aWNlKQpgYGAKCkFsc28gcHJlZGljdCByZWNhbGwgdXNpbmcgdGhlIHBhcmFtZXRyaWMgZnVuY3Rpb246CmBgYHtyIHByZWRpY3QtcGFyYW1ldHJpY30KIyBQcmVkaWN0IHJlY2FsbCB1c2luZyB0aGUgbGluZWFyIG1vZGVsCnByZWRpY3RfbG0gPC0gZnVuY3Rpb24gKHN1YnNldCwgc3ViX2xhYmVsLCBsbV90YXUsIGxtX2QsIGxtX2gpIHsKICAKICBkX3ByZWQgPC0gZF9sYXN0WywgLihpZCwgdXNlciwgdHJpYWxzLCB0aW1lX2JldHdlZW4sIGNvcnJlY3QpXQogIGlmIChzdWJzZXQgPT0gImJ5X2xlYXJuZXIiKSB7CiAgICBkX3ByZWQgPC0gZF9wcmVkW3VzZXIgPT0gc3ViX2xhYmVsXQogIH0gZWxzZSBpZiAoc3Vic2V0ID09ICJieV9wcmFjdGljZSIpIHsKICAgIGRfcHJlZCA8LSBkX3ByZWRbdHJpYWxzID09IHN1Yl9sYWJlbF0KICB9CiAgCiAgIyBHZXQgbW9kZWwgcGFyYW1ldGVycyBwZXIgc2VxdWVuY2UKICB0YXVfZml0IDwtIHByZWRpY3QobG1fdGF1LCBuZXdkYXRhID0gZF9wcmVkWywgLihnZW9tX21lYW4gPSB0aW1lX2JldHdlZW4pXSkKICBkX2ZpdCA8LSBwcmVkaWN0KGxtX2QsIG5ld2RhdGEgPSBkX3ByZWRbLCAuKGdlb21fbWVhbiA9IHRpbWVfYmV0d2VlbildKQogIGhfZml0IDwtIHByZWRpY3QobG1faCwgbmV3ZGF0YSA9IGRfcHJlZFssIC4oZ2VvbV9tZWFuID0gdGltZV9iZXR3ZWVuKV0pIHw+IGV4cCgpCiAgCiAgcGFyYW1zX2ZpdCA8LSBjYmluZChkX3ByZWRbLCAuKGlkKV0sIHRhdV9maXQsIGRfZml0LCBoX2ZpdCkKICAKICAjIFByZXBhcmUgc2VxdWVuY2VzCiAgZF9wcmVkWywgc2VxdWVuY2UgOj0gMTouTl0KICBkX3ByZWRbLCB3aW5kb3cgOj0gMF0KICBkX3ByZWRfZnVsbCA8LSBkX2ZbZF9wcmVkWywgLihpZCwgc2VxdWVuY2UsIHdpbmRvdyldLCBvbiA9IC4oaWQpXQogIGRfcHJlZF9zZXFzIDwtIGdlbmVyYXRlX3NlcV9saXN0KGRfcHJlZF9mdWxsKQogIAogICMgdGF1CiAgYWNfdGF1IDwtIG1hcF9kYmwoZF9wcmVkX3NlcXMsIGZ1bmN0aW9uICh4KSB7CiAgICBhY3RpdmF0aW9uKHgkdGltZV93aXRoaW4sIHgkdGltZV9iZXR3ZWVuLCBtb2RlbF9wYXJhbXMkaCwgbW9kZWxfcGFyYW1zJGRlY2F5KQogIH0pCiAgcF9yZWNhbGxfdGF1IDwtIHBfcmVjYWxsKGFjX3RhdSwgcGFyYW1zX2ZpdCR0YXVfZml0LCBtb2RlbF9wYXJhbXMkcykKCiAgIyBkCiAgYWNfZCA8LSBtYXAyX2RibChkX3ByZWRfc2VxcywgcGFyYW1zX2ZpdCRkX2ZpdCwgZnVuY3Rpb24gKHgsIGRfeCkgewogICAgYWN0aXZhdGlvbih4JHRpbWVfd2l0aGluLCB4JHRpbWVfYmV0d2VlbiwgbW9kZWxfcGFyYW1zJGgsIGRfeCkKICB9KQogIHBfcmVjYWxsX2QgPC0gcF9yZWNhbGwoYWNfZCwgbW9kZWxfcGFyYW1zJHRhdSwgbW9kZWxfcGFyYW1zJHMpCiAgCiAgIyBoCiAgYWNfaCA8LSBtYXAyX2RibChkX3ByZWRfc2VxcywgcGFyYW1zX2ZpdCRoX2ZpdCwgZnVuY3Rpb24gKHgsIGhfeCkgewogICAgYWN0aXZhdGlvbih4JHRpbWVfd2l0aGluLCB4JHRpbWVfYmV0d2VlbiwgaF94LCBtb2RlbF9wYXJhbXMkZGVjYXkpCiAgfSkKICBwX3JlY2FsbF9oIDwtIHBfcmVjYWxsKGFjX2gsIG1vZGVsX3BhcmFtcyR0YXUsIG1vZGVsX3BhcmFtcyRzKQogIAogIHJldHVybiAoZGF0YS50YWJsZShzdWJzZXQgPSBzdWJzZXQsCiAgICAgICAgICAgICAgICAgICAgIHN1Yl9sYWJlbCA9IHN1Yl9sYWJlbCwKICAgICAgICAgICAgICAgICAgICAgd2luZG93X2lkID0gMCwKICAgICAgICAgICAgICAgICAgICAgbl93aW5kb3dzID0gMjAsCiAgICAgICAgICAgICAgICAgICAgIHdpbmRvdyA9IDAsCiAgICAgICAgICAgICAgICAgICAgIGdlb21fbWVhbiA9IE5BLAogICAgICAgICAgICAgICAgICAgICB3aW5kb3dfdHlwZSA9ICJsbSIsCiAgICAgICAgICAgICAgICAgICAgIGlkID0gZF9wcmVkJGlkLAogICAgICAgICAgICAgICAgICAgICB0aW1lX2JldHdlZW4gPSBkX3ByZWQkdGltZV9iZXR3ZWVuLAogICAgICAgICAgICAgICAgICAgICBjb3JyZWN0ID0gZF9wcmVkJGNvcnJlY3QsCiAgICAgICAgICAgICAgICAgICAgIHBfcmVjYWxsX3RhdSA9IHBfcmVjYWxsX3RhdSwKICAgICAgICAgICAgICAgICAgICAgcF9yZWNhbGxfZCA9IHBfcmVjYWxsX2QsCiAgICAgICAgICAgICAgICAgICAgIHBfcmVjYWxsX2ggPSBwX3JlY2FsbF9oKSkKICAKfQoKCnByZWRfbG1fcGF0aCA8LSBoZXJlKCJkYXRhIiwgInByZWRfbG0uY3N2IikKCmlmICghdXNlX3NhdmVkX3ByZWRpY3Rpb25zIHwgIWZpbGUuZXhpc3RzKHByZWRfbG1fcGF0aCkpIHsKICBwcmVkX2xtIDwtIGZ1dHVyZV9tYXAoc2VxX2xlbihucm93KGxtX2ZpdCkpLCBmdW5jdGlvbiAoeCkgewogICAgbG1fZml0W3gsIHByZWRpY3RfbG0oc3Vic2V0LCBzdWJfbGFiZWwsIGxtX3RhdVtbMV1dLCBsbV9kW1sxXV0sIGxtX2hbWzFdXSldCiAgfSkgfD4KICAgIHJiaW5kbGlzdCgpCiAgZndyaXRlKHByZWRfbG0sIHByZWRfbG1fcGF0aCkKfSBlbHNlIHsKICBwcmVkX2xtIDwtIGZyZWFkKHByZWRfbG1fcGF0aCkKfQoKcHJlZHMgPC0gcmJpbmQocHJlZHMsIHByZWRfbG0pCmBgYAoKIyMgVmlzdWFsaXNlIGZpdAoKCmBgYHtyIHBsb3QtY29tcGFyaXNvbn0KcGxvdF9jb21wYXJpc29uIDwtIGZ1bmN0aW9uIChkX21vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfbGFzdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aW5kb3dfcmFuZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41NCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC40NikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Bsb3QgPSBUUlVFKSB7CiAgCiAgCiAgcGxvdF9kb2RnZSA8LSBmdW5jdGlvbih5LCBkb2RnZSA9IC4xKSB7CiAgICByZXR1cm4gKHkgKiAoMSArIGRvZGdlKSAtIGRvZGdlLzIpCiAgfQogIAogIHAgPC0gZ2dwbG90KCkgKwogICAgIyBXaW5kb3cgYmFja2dyb3VuZAogICAgZ2VvbV9yZWN0KGRhdGEgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IG5fd10sCiAgICAgICAgICAgICAgYWVzKHhtaW4gPSBzdGFydC82MCwgeG1heCA9IGlmZWxzZShpcy5uYShzaGlmdChzdGFydCwgLTEpKSwgZW5kLCBzaGlmdChzdGFydCwgLTEpKS82MCwKICAgICAgICAgICAgICAgICAgeW1pbiA9IC1JbmYsIHltYXggPSBJbmYsIGFscGhhID0gYXMuZmFjdG9yKHdpbmRvdykpLAogICAgICAgICAgICAgIGZpbGwgPSB3aW5kb3dfY29sKSArCiAgICAjIEppdHRlcmVkIG9ic2VydmF0aW9ucyBhbG9uZyBlZGdlcwogICAgZ2VvbV9wb2ludChkYXRhID0gZF9sYXN0LCAKICAgICAgICAgICAgICAgYWVzKHggPSB0aW1lX2JldHdlZW4vNjAsIHkgPSBwbG90X2RvZGdlKGNvcnJlY3QsIC4wNSkpLAogICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAsIGhlaWdodCA9IC4wMjUsIHNlZWQgPSAxMjMpLAogICAgICAgICAgICAgICBjb2xvdXIgPSBvYnNfY29sLCBzaXplID0gLjAwMSwgcGNoID0gIi4iLCBhbHBoYSA9IC4xKSArCiAgICAjIFByZWRpY3Rpb25zIG9mIHRoZSBtb2RlbAogICAgIyBnZW9tX3BvaW50KGRhdGEgPSBkX21vZGVsLCAKICAgICMgICAgICAgICAgICBhZXMoeCA9IHRpbWVfYmV0d2Vlbi82MCwgeSA9IHByZWRfY29ycmVjdCksCiAgICAjICAgICAgICAgICAgY29sb3VyID0gcHJlZF9jb2wsIGFscGhhID0gLjAxKSArCiAgICAjIEdBTTogZGF0YQogICAgZ2VvbV9zbW9vdGgoZGF0YSA9IGRfbGFzdCwKICAgICAgICAgICAgICAgIGFlcyh4ID0gdGltZV9iZXR3ZWVuLzYwLCB5ID0gY29ycmVjdCksCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiZ2FtIiwgZm9ybXVsYSA9IHkgfiBzKHgsIGJzID0gImNzIiksCiAgICAgICAgICAgICAgICBjb2xvdXIgPSBvYnNfY29sLCBsdHkgPSAxLCBsd2QgPSAxKSArCiAgICAjIEdBTTogbW9kZWwKICAgIGdlb21fc21vb3RoKGRhdGEgPSBkX21vZGVsLCAKICAgICAgICAgICAgICAgIGFlcyh4ID0gdGltZV9iZXR3ZWVuLzYwLCB5ID0gcHJlZF9jb3JyZWN0KSwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnYW0iLCBmb3JtdWxhID0geSB+IHMoeCwgYnMgPSAiY3MiKSwgCiAgICAgICAgICAgICAgICBjb2xvdXIgPSBwcmVkX2NvbCwgZmlsbCA9IHByZWRfY29sLCBsdHkgPSAxLCBsd2QgPSAuNzUpICsKICAgICMgTGFiZWxzCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSBsYWJlbF9wb3MkZGF0YSR4LCB5ID0gbGFiZWxfcG9zJGRhdGEkeSwKICAgICAgICAgICAgIGxhYmVsID0gIkRhdGEiLCBjb2xvdXIgPSBvYnNfY29sKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSBsYWJlbF9wb3MkbW9kZWwkeCwgeSA9IGxhYmVsX3BvcyRtb2RlbCR5LAogICAgICAgICAgICAgbGFiZWwgPSAiTW9kZWwiLCBjb2xvdXIgPSBwcmVkX2NvbCkgKwogICAgIyBQbG90IHNldHVwCiAgICBzY2FsZV94X2xvZzEwKAogICAgICBicmVha3MgPSBzY2FsZXM6OnRyYW5zX2JyZWFrcygibG9nMTAiLCBmdW5jdGlvbih4KSAxMF54KSwKICAgICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIiwgc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpLAogICAgICBleHBhbmQgPSBjKDAsIDApLAogICAgICBzZWMuYXhpcyA9IHNlY19heGlzKH4ueCwgYnJlYWtzID0gbGFiZWxfeCwgbGFiZWxzID0gbGFiZWxfdHh0KQogICAgKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEsIGJ5ID0gLjI1KSwgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArCiAgICBzY2FsZV9hbHBoYV9tYW51YWwodmFsdWVzID0gcmVwKGMoLjEsIC4yNSksIGNlaWxpbmcobl93LzIpKSkgKwogICAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwKICAgICAgICAgICBhbHBoYSA9ICJub25lIikgKwogICAgbGFicyh4ID0gIkJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAobWludXRlcykiLAogICAgICAgICB5ID0gIlJlc3BvbnNlIGFjY3VyYWN5IikgKwogICAgYW5ub3RhdGlvbl9sb2d0aWNrcyhzaWRlcyA9ICJiIiwgb3V0c2lkZSA9IFQpICsKICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxKSwgeGxpbSA9IGMod2luZG93X3JhbmdlWzEsIHN0YXJ0XSwgd2luZG93X3JhbmdlWy5OLCBlbmRdKS82MCwgY2xpcCA9ICJvZmYiKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgKwogICAgdGhlbWUocGxvdC5tYXJnaW4gPSBtYXJnaW4oNywgMTQsIDcsIDcpLAogICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gOCkpLAogICAgICAgICAgYXhpcy50ZXh0LngudG9wID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbihiID0gOCkpKQogIAogIGlmIChwcmludF9wbG90KSBwcmludChwKQogIHJldHVybiAocCkKICAgIAp9CmBgYAoKIyMjIFJlZ3VsYXIgZml0CgojIyMjIFdpbmRvdyBzcGxpdHMKClRhdSBmaXR0ZWQgdG8gdmFyaW91cyB3aW5kb3cgc3BsaXRzOgpgYGB7cn0KcHJlZF90YXUgPC0gY29weShwcmVkcykKc2V0bmFtZXMocHJlZF90YXUsICJwX3JlY2FsbF90YXUiLCAicHJlZF9jb3JyZWN0IikKCnBfdGF1X3dpbmRvd3MgPC0gbWFwKG5fd2luZG93cywgZnVuY3Rpb24gKG5fdykgewogIHAgPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBwcmVkX3RhdVtzdWJzZXQgPT0gImFsbCIgJiBuX3dpbmRvd3MgPT0gbl93ICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgZF9sYXN0ID0gcHJlZF90YXVbc3Vic2V0ID09ICJhbGwiICYgbl93aW5kb3dzID09IG5fdyAmIHdpbmRvd190eXBlID09ICJyZWd1bGFyIl0sCiAgICAgICAgICAgICAgICAgICAgICAgd2luZG93X3JhbmdlID0gd2luZG93X3JhbmdlW25fd2luZG93cyA9PSBuX3cgJiB3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLAogICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IG5fdywgCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAyMDAwMCwgeSA9IC4zNSkpKQogIHJldHVybiAocCkKfSkKYGBgCgpEZWNheSBmaXR0ZWQgdG8gdmFyaW91cyB3aW5kb3cgc3BsaXRzOgpgYGB7cn0KcHJlZF9kIDwtIGNvcHkocHJlZHMpCnNldG5hbWVzKHByZWRfZCwgInBfcmVjYWxsX2QiLCAicHJlZF9jb3JyZWN0IikKCnBfZF93aW5kb3dzIDwtIG1hcChuX3dpbmRvd3MsIGZ1bmN0aW9uIChuX3cpIHsKICBwIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gcHJlZF9kW3N1YnNldCA9PSAiYWxsIiAmIG5fd2luZG93cyA9PSBuX3cgJiB3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLCAKICAgICAgICAgICAgICAgICAgICAgICBkX2xhc3QgPSBwcmVkX2Rbc3Vic2V0ID09ICJhbGwiICYgbl93aW5kb3dzID09IG5fdyAmIHdpbmRvd190eXBlID09ICJyZWd1bGFyIl0sCiAgICAgICAgICAgICAgICAgICAgICAgd2luZG93X3JhbmdlID0gd2luZG93X3JhbmdlW25fd2luZG93cyA9PSBuX3cgJiB3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLAogICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IG5fdywgCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAyMDAwMCwgeSA9IC4zNSkpKQogIHJldHVybiAocCkKfSkKYGBgClNjYWxpbmcgZmFjdG9yIGZpdHRlZCB0byB2YXJpb3VzIHdpbmRvdyBzcGxpdHM6CmBgYHtyfQpwcmVkX2ggPC0gY29weShwcmVkcykKc2V0bmFtZXMocHJlZF9oLCAicF9yZWNhbGxfaCIsICJwcmVkX2NvcnJlY3QiKQoKcF9oX3dpbmRvd3MgPC0gbWFwKG5fd2luZG93cywgZnVuY3Rpb24gKG5fdykgewogIHAgPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBwcmVkX2hbc3Vic2V0ID09ICJhbGwiICYgbl93aW5kb3dzID09IG5fdyAmIHdpbmRvd190eXBlID09ICJyZWd1bGFyIl0sIAogICAgICAgICAgICAgICAgICAgICAgIGRfbGFzdCA9IHByZWRfaFtzdWJzZXQgPT0gImFsbCIgJiBuX3dpbmRvd3MgPT0gbl93ICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwKICAgICAgICAgICAgICAgICAgICAgICB3aW5kb3dfcmFuZ2UgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IG5fdyAmIHdpbmRvd190eXBlID09ICJyZWd1bGFyIl0sCiAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gbl93LCAKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGxpc3QoeCA9IDIwMDAwLCB5ID0gLjM1KSkpCiAgcmV0dXJuIChwKQp9KQpgYGAKIyMjIyBTaG9ydCBpbnRlcnZhbHMKClRhdSBmaXR0ZWQgdG8gc2hvcnQgaW50ZXJ2YWxzOgpgYGB7cn0KcF90YXVfc2hvcnQgPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBwcmVkX3RhdVtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAic2hvcnQiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX2xhc3QgPSBwcmVkX3RhdVtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAic2hvcnQiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aW5kb3dfcmFuZ2UgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IDEgJiB3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjA4KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9wbG90ID0gRkFMU0UpICsKICBnZW9tX3JlY3QoYWVzKHhtaW4gID0gd2luZG93X3JhbmdlW3dpbmRvd190eXBlID09ICJzaG9ydCIsIHN0YXJ0LzYwXSwgeG1heCA9IHdpbmRvd19yYW5nZVt3aW5kb3dfdHlwZSA9PSAic2hvcnQiLCBlbmQvNjBdLCB5bWluID0gLTAuMDUsIHltYXggPSAxLjA1KSwgZmlsbCA9IHNlY3Rpb25fY29sLCBhbHBoYSA9IC4yNSkKCnBfdGF1X3Nob3J0CmBgYApEZWNheSBmaXR0ZWQgdG8gc2hvcnQgaW50ZXJ2YWxzOgpgYGB7cn0KcF9kX3Nob3J0IDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gcHJlZF9kW3N1YnNldCA9PSAiYWxsIiAmIHdpbmRvd190eXBlID09ICJzaG9ydCJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX2xhc3QgPSBwcmVkX2Rbc3Vic2V0ID09ICJhbGwiICYgd2luZG93X3R5cGUgPT0gInNob3J0Il0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpbmRvd19yYW5nZSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMSAmIHdpbmRvd190eXBlID09ICJyZWd1bGFyIl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3BvcyA9IGxpc3QoZGF0YSA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuMDgpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9wbG90ID0gRkFMU0UpICsKICBnZW9tX3JlY3QoYWVzKHhtaW4gID0gd2luZG93X3JhbmdlW3dpbmRvd190eXBlID09ICJzaG9ydCIsIHN0YXJ0LzYwXSwgeG1heCA9IHdpbmRvd19yYW5nZVt3aW5kb3dfdHlwZSA9PSAic2hvcnQiLCBlbmQvNjBdLCB5bWluID0gLTAuMDUsIHltYXggPSAxLjA1KSwgZmlsbCA9IHNlY3Rpb25fY29sLCBhbHBoYSA9IC4yNSkKCnBfZF9zaG9ydApgYGAKClNjYWxpbmcgZmFjdG9yIGZpdHRlZCB0byBzaG9ydCBpbnRlcnZhbHM6CmBgYHtyfQpwX2hfc2hvcnQgPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBwcmVkX2hbc3Vic2V0ID09ICJhbGwiICYgd2luZG93X3R5cGUgPT0gInNob3J0Il0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfbGFzdCA9IHByZWRfaFtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAic2hvcnQiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2luZG93X3JhbmdlID0gd2luZG93X3JhbmdlW25fd2luZG93cyA9PSAxICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC4wOCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Bsb3QgPSBGQUxTRSkgKwogIGdlb21fcmVjdChhZXMoeG1pbiAgPSB3aW5kb3dfcmFuZ2Vbd2luZG93X3R5cGUgPT0gInNob3J0Iiwgc3RhcnQvNjBdLCB4bWF4ID0gd2luZG93X3JhbmdlW3dpbmRvd190eXBlID09ICJzaG9ydCIsIGVuZC82MF0sIHltaW4gPSAtMC4wNSwgeW1heCA9IDEuMDUpLCBmaWxsID0gc2VjdGlvbl9jb2wsIGFscGhhID0gLjI1KQoKcF9oX3Nob3J0CmBgYAoKIyMjIyAyNGggaW50ZXJ2YWxzCgpUYXUgZml0dGVkIHRvIDI0IGg6CmBgYHtyfQpwX3RhdV8yNGggPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBwcmVkX3RhdVtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAiMjRoIl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfbGFzdCA9IHByZWRfdGF1W3N1YnNldCA9PSAiYWxsIiAmIHdpbmRvd190eXBlID09ICIyNGgiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2luZG93X3JhbmdlID0gd2luZG93X3JhbmdlW25fd2luZG93cyA9PSAxICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSA1MDAwLCB5ID0gLjI5KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfcGxvdCA9IEZBTFNFKSArCiAgZ2VvbV9yZWN0KGFlcyh4bWluICA9IHdpbmRvd19yYW5nZVt3aW5kb3dfdHlwZSA9PSAiMjRoIiwgc3RhcnQvNjBdLCB4bWF4ID0gd2luZG93X3JhbmdlW3dpbmRvd190eXBlID09ICIyNGgiLCBlbmQvNjBdLCB5bWluID0gLTAuMDUsIHltYXggPSAxLjA1KSwgZmlsbCA9IHNlY3Rpb25fY29sLCBhbHBoYSA9IC4yNSkKCnBfdGF1XzI0aApgYGAKCkRlY2F5IGZpdHRlZCB0byAyNCBoOgpgYGB7cn0KcF9kXzI0aCA8LSBwbG90X2NvbXBhcmlzb24oZF9tb2RlbCA9IHByZWRfZFtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAiMjRoIl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkX2xhc3QgPSBwcmVkX2Rbc3Vic2V0ID09ICJhbGwiICYgd2luZG93X3R5cGUgPT0gIjI0aCJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgd2luZG93X3JhbmdlID0gd2luZG93X3JhbmdlW25fd2luZG93cyA9PSAxICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fdyA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSA1MDAwLCB5ID0gLjc4KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Bsb3QgPSBGQUxTRSkgKwogIGdlb21fcmVjdChhZXMoeG1pbiAgPSB3aW5kb3dfcmFuZ2Vbd2luZG93X3R5cGUgPT0gIjI0aCIsIHN0YXJ0LzYwXSwgeG1heCA9IHdpbmRvd19yYW5nZVt3aW5kb3dfdHlwZSA9PSAiMjRoIiwgZW5kLzYwXSwgeW1pbiA9IC0wLjA1LCB5bWF4ID0gMS4wNSksIGZpbGwgPSBzZWN0aW9uX2NvbCwgYWxwaGEgPSAuMjUpCgpwX2RfMjRoCmBgYAoKU2NhbGluZyBmYWN0b3IgZml0dGVkIHRvIDI0IGg6CmBgYHtyfQpwX2hfMjRoIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gcHJlZF9oW3N1YnNldCA9PSAiYWxsIiAmIHdpbmRvd190eXBlID09ICIyNGgiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfbGFzdCA9IHByZWRfaFtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAiMjRoIl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICB3aW5kb3dfcmFuZ2UgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IDEgJiB3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3BvcyA9IGxpc3QoZGF0YSA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGxpc3QoeCA9IDUwMDAsIHkgPSAuNzgpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfcGxvdCA9IEZBTFNFKSArCiAgZ2VvbV9yZWN0KGFlcyh4bWluICA9IHdpbmRvd19yYW5nZVt3aW5kb3dfdHlwZSA9PSAiMjRoIiwgc3RhcnQvNjBdLCB4bWF4ID0gd2luZG93X3JhbmdlW3dpbmRvd190eXBlID09ICIyNGgiLCBlbmQvNjBdLCB5bWluID0gLTAuMDUsIHltYXggPSAxLjA1KSwgZmlsbCA9IHNlY3Rpb25fY29sLCBhbHBoYSA9IC4yNSkKCnBfaF8yNGgKYGBgCgojIyMjIFBhcmFtZXRyaWMgZml0CgpUYXUgZml0dGVkIHVzaW5nIHRoZSBwYXJhbWV0cmljIGZ1bmN0aW9uICh0YXUodCkpOgpgYGB7cn0KcF90YXVfbG0gPC0gcGxvdF9jb21wYXJpc29uKGRfbW9kZWwgPSBwcmVkX3RhdVtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAibG0iXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX2xhc3QgPSBwcmVkX3RhdVtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAibG0iXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aW5kb3dfcmFuZ2UgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IDIwICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wb3MgPSBsaXN0KGRhdGEgPSBsaXN0KHggPSAzNTAwMCwgeSA9IC41KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gbGlzdCh4ID0gNTAwMCwgeSA9IC43KSkpCmBgYAoKRGVjYXkgZml0dGVkIHVzaW5nIHRoZSBwYXJhbWV0cmljIGZ1bmN0aW9uIChkKHQpKToKYGBge3J9CnBfZF9sbSA8LSBwbG90X2NvbXBhcmlzb24oZF9tb2RlbCA9IHByZWRfZFtzdWJzZXQgPT0gImFsbCIgJiB3aW5kb3dfdHlwZSA9PSAibG0iXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgZF9sYXN0ID0gcHJlZF9kW3N1YnNldCA9PSAiYWxsIiAmIHdpbmRvd190eXBlID09ICJsbSJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICB3aW5kb3dfcmFuZ2UgPSB3aW5kb3dfcmFuZ2Vbbl93aW5kb3dzID09IDIwICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbl93ID0gMjAsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsX3BvcyA9IGxpc3QoZGF0YSA9IGxpc3QoeCA9IDM1MDAwLCB5ID0gLjI1KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IGxpc3QoeCA9IDIwMDAwLCB5ID0gLjYpKSkKYGBgCgpTY2FsaW5nIGZhY3RvciBmaXR0ZWQgdXNpbmcgdGhlIHBhcmFtZXRyaWMgZnVuY3Rpb24gKGgodCkpOgpgYGB7cn0KcF9oX2xtIDwtIHBsb3RfY29tcGFyaXNvbihkX21vZGVsID0gcHJlZF9oW3N1YnNldCA9PSAiYWxsIiAmIHdpbmRvd190eXBlID09ICJsbSJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBkX2xhc3QgPSBwcmVkX2hbc3Vic2V0ID09ICJhbGwiICYgd2luZG93X3R5cGUgPT0gImxtIl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgIHdpbmRvd19yYW5nZSA9IHdpbmRvd19yYW5nZVtuX3dpbmRvd3MgPT0gMjAgJiB3aW5kb3dfdHlwZSA9PSAicmVndWxhciJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBuX3cgPSAyMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcG9zID0gbGlzdChkYXRhID0gbGlzdCh4ID0gMzUwMDAsIHkgPSAuNSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBsaXN0KHggPSAyMDAwMCwgeSA9IC4zNSkpKQpgYGAKCgoKIyMjIEZpdCBieSBsZWFybmVyCgpBdCB0aGUgbGV2ZWwgb2YgdGhlIGluZGl2aWR1YWwgbGVhcm5lciwgdGhlIGRhdGEgaXMgcXVpdGUgc3BhcnNlLgpUaGUgcGxvdCBiZWxvdyBwcm92aWRlcyBhIHNhbXBsZSBvZiBsZWFybmVyLXNwZWNpZmljIGZpdHMgZm9yIHRhdSwgYmFzZWQgb24gYSAyMC13aW5kb3cgc3BsaXQuClRoZSByZWQgcG9pbnRzIGFyZSBpbmRpdmlkdWFsIHByZWRpY3Rpb25zIChsaWdodCkgYW5kIGF2ZXJhZ2VzIChkYXJrKTsgdGhlIGJsYWNrIHBvaW50cyB0aGUgb2JzZXJ2ZWQgcmVjYWxsLgoKYGBge3J9CnNldC5zZWVkKDApCgpwcmVkXzIwX2xlYXJuZXIgPC0gY29weShwcmVkc1tzdWJzZXQgPT0gImJ5X2xlYXJuZXIiICYgbl93aW5kb3dzID09IDIwICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSkKcHJlZF8yMF9sZWFybmVyX2F2ZyA8LSBwcmVkXzIwX2xlYXJuZXJbLCAuKGNvcnJlY3QgPSBtZWFuKGNvcnJlY3QpLCBwX3JlY2FsbF90YXUgPSBtZWFuKHBfcmVjYWxsX3RhdSksIHBfcmVjYWxsX2QgPSBtZWFuKHBfcmVjYWxsX2QpLCBwX3JlY2FsbF9oID0gbWVhbihwX3JlY2FsbF9oKSksIGJ5ID0gLihzdWJfbGFiZWwsIG5fd2luZG93cywgd2luZG93X3R5cGUsIHdpbmRvdywgZ2VvbV9tZWFuKV0KCnNhbXBsZV9sZWFybmVycyA8LSBzYW1wbGUodW5pcXVlKHByZWRfMjBfbGVhcm5lcl9hdmckc3ViX2xhYmVsKSwgMTgsIHJlcGxhY2UgPSBGQUxTRSkKCmdncGxvdChwcmVkXzIwX2xlYXJuZXJbc3ViX2xhYmVsICVpbiUgc2FtcGxlX2xlYXJuZXJzXSwgYWVzKHggPSB0aW1lX2JldHdlZW4vNjAsIHkgPSBwX3JlY2FsbF90YXUsIGdyb3VwID0gc3ViX2xhYmVsKSkgKwogIGZhY2V0X3dyYXAofiBzdWJfbGFiZWwsIG5jb2wgPSA2KSArCiAgZ2VvbV9wb2ludChhZXMoeSA9IGNvcnJlY3QpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAsIGhlaWdodCA9IC4wMjUpLCBhbHBoYSA9IC4xLCBjb2xvdXIgPSAiYmxhY2siKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC4wNSwgY29sb3VyID0gInJlZCIpICsKICBnZW9tX3BvaW50KGRhdGEgPSBwcmVkXzIwX2xlYXJuZXJfYXZnW3N1Yl9sYWJlbCAlaW4lIHNhbXBsZV9sZWFybmVyc10sIGFlcyggeD0gZ2VvbV9tZWFuLzYwLCB5ID0gcF9yZWNhbGxfdGF1KSwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJQcmVkaWN0ZWQgcmVjYWxsIiwgY29sb3VyID0gIkxlYXJuZXIiKQpgYGAKClRoZSBzYW1lIHBsb3QgZm90IGZpdHRlZCBkZWNheToKYGBge3J9CmdncGxvdChwcmVkXzIwX2xlYXJuZXJbc3ViX2xhYmVsICVpbiUgc2FtcGxlX2xlYXJuZXJzXSwgYWVzKHggPSB0aW1lX2JldHdlZW4vNjAsIHkgPSBwX3JlY2FsbF9kLCBncm91cCA9IHN1Yl9sYWJlbCkpICsKICBmYWNldF93cmFwKH4gc3ViX2xhYmVsLCBuY29sID0gNikgKwogIGdlb21fcG9pbnQoYWVzKHkgPSBjb3JyZWN0KSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLCBoZWlnaHQgPSAuMDI1KSwgYWxwaGEgPSAuMSwgY29sb3VyID0gImJsYWNrIikgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMDUsIGNvbG91ciA9ICJyZWQiKSArCiAgZ2VvbV9wb2ludChkYXRhID0gcHJlZF8yMF9sZWFybmVyX2F2Z1tzdWJfbGFiZWwgJWluJSBzYW1wbGVfbGVhcm5lcnNdLCBhZXMoIHg9IGdlb21fbWVhbi82MCwgeSA9IHBfcmVjYWxsX2QpLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDIpICsKICBwbG90X3RpbWVzY2FsZXMoKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIikgKwogIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbikiLCB5ID0gIlByZWRpY3RlZCByZWNhbGwiLCBjb2xvdXIgPSAiTGVhcm5lciIpCmBgYAoKVGhlIHNhbWUgcGxvdCBmb3IgZml0dGVkIHNjYWxpbmcgZmFjdG9yIGg6CmBgYHtyfQpnZ3Bsb3QocHJlZF8yMF9sZWFybmVyW3N1Yl9sYWJlbCAlaW4lIHNhbXBsZV9sZWFybmVyc10sIGFlcyh4ID0gdGltZV9iZXR3ZWVuLzYwLCB5ID0gcF9yZWNhbGxfaCwgZ3JvdXAgPSBzdWJfbGFiZWwpKSArCiAgZmFjZXRfd3JhcCh+IHN1Yl9sYWJlbCwgbmNvbCA9IDYpICsKICBnZW9tX3BvaW50KGFlcyh5ID0gY29ycmVjdCksIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMCwgaGVpZ2h0ID0gLjAyNSksIGFscGhhID0gLjEsIGNvbG91ciA9ICJibGFjayIpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjA1LCBjb2xvdXIgPSAicmVkIikgKwogIGdlb21fcG9pbnQoZGF0YSA9IHByZWRfMjBfbGVhcm5lcl9hdmdbc3ViX2xhYmVsICVpbiUgc2FtcGxlX2xlYXJuZXJzXSwgYWVzKCB4PSBnZW9tX21lYW4vNjAsIHkgPSBwX3JlY2FsbF9oKSwgY29sb3VyID0gInJlZCIsIHNpemUgPSAyKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJQcmVkaWN0ZWQgcmVjYWxsIiwgY29sb3VyID0gIkxlYXJuZXIiKQpgYGAKCgoKCiMjIyBGaXQgYnkgcHJhY3RpY2UKCkZpdHRlZCB0YXUgYnkgYW1vdW50IG9mIHByYWN0aWNlOgpgYGB7cn0KcHJlZF8yMF9wcmFjdGljZSA8LSBjb3B5KHByZWRzW3N1YnNldCA9PSAiYnlfcHJhY3RpY2UiICYgbl93aW5kb3dzID09IDIwICYgd2luZG93X3R5cGUgPT0gInJlZ3VsYXIiXSkKcHJlZF8yMF9wcmFjdGljZV9hdmcgPC0gcHJlZF8yMF9wcmFjdGljZVssIC4oY29ycmVjdCA9IG1lYW4oY29ycmVjdCksIHBfcmVjYWxsX3RhdSA9IG1lYW4ocF9yZWNhbGxfdGF1KSwgcF9yZWNhbGxfZCA9IG1lYW4ocF9yZWNhbGxfZCksIHBfcmVjYWxsX2ggPSBtZWFuKHBfcmVjYWxsX2gpKSwgYnkgPSAuKHN1Yl9sYWJlbCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSwgd2luZG93LCBnZW9tX21lYW4pXQoKZ2dwbG90KHByZWRfMjBfcHJhY3RpY2VfYXZnLCBhZXMoeCA9IGdlb21fbWVhbi82MCwgeSA9IHBfcmVjYWxsX3RhdSwgZ3JvdXAgPSBzdWJfbGFiZWwsIGNvbG91ciA9IHN1Yl9sYWJlbCkpICsKICBmYWNldF93cmFwKH4gc3ViX2xhYmVsLCBuY29sID0gNikgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBzdWJfbGFiZWwpKSArCiAgcGxvdF90aW1lc2NhbGVzKCkgKwogIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKG1pbikiLCB5ID0gIlByZWRpY3RlZCByZWNhbGwiLCBjb2xvdXIgPSAiVHJpYWxzIikKYGBgCgpGaXR0ZWQgZGVjYXkgYnkgYW1vdW50IG9mIHByYWN0aWNlOgpgYGB7cn0KZ2dwbG90KHByZWRfMjBfcHJhY3RpY2VfYXZnLCBhZXMoeCA9IGdlb21fbWVhbi82MCwgeSA9IHBfcmVjYWxsX2QsIGdyb3VwID0gc3ViX2xhYmVsLCBjb2xvdXIgPSBzdWJfbGFiZWwpKSArCiAgZmFjZXRfd3JhcCh+IHN1Yl9sYWJlbCwgbmNvbCA9IDYpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gc3ViX2xhYmVsKSkgKwogIHBsb3RfdGltZXNjYWxlcygpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJQcmVkaWN0ZWQgcmVjYWxsIiwgY29sb3VyID0gIlRyaWFscyIpCmBgYAoKRml0dGVkIGggYnkgYW1vdW50IG9mIHByYWN0aWNlOgpgYGB7cn0KZ2dwbG90KHByZWRfMjBfcHJhY3RpY2VfYXZnLCBhZXMoeCA9IGdlb21fbWVhbi82MCwgeSA9IHBfcmVjYWxsX2gsIGdyb3VwID0gc3ViX2xhYmVsLCBjb2xvdXIgPSBzdWJfbGFiZWwpKSArCiAgZmFjZXRfd3JhcCh+IHN1Yl9sYWJlbCwgbmNvbCA9IDYpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gc3ViX2xhYmVsKSkgKwogIHBsb3RfdGltZXNjYWxlcygpICsKICBsYWJzKHggPSAiQmV0d2Vlbi1zZXNzaW9uIGludGVydmFsIChtaW4pIiwgeSA9ICJQcmVkaWN0ZWQgcmVjYWxsIiwgY29sb3VyID0gIlRyaWFscyIpCmBgYAoKV2VyZSBzb21lIGFtb3VudHMgb2YgcHJpb3IgcHJhY3RpY2UgbW9yZSBwcmV2YWxlbnQgaW4gY2VydGFpbiB3aW5kb3dzPwpOb3QgcmVhbGx5LCB0aGUgZGlzdHJpYnV0aW9uIGxvb2tzIGZhaXJseSBzaW1pbGFyIGFjcm9zcyBkaWZmZXJlbnQgYW1vdW50cyBvZiBwcmFjdGljZSAoZmFjZXRzKToKYGBge3J9CmdncGxvdChwcmVkXzIwX3ByYWN0aWNlLCBhZXMoeCA9IHdpbmRvdywgZmlsbCA9IGFzLmZhY3Rvcih3aW5kb3cpKSkgKwogIGZhY2V0X3dyYXAofiBzdWJfbGFiZWwsIG5jb2wgPSA2LCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGxhYnMoeCA9ICJCZXR3ZWVuLXNlc3Npb24gaW50ZXJ2YWwgKHdpbmRvdykiLCB5ID0gIkNvdW50IiwgZmlsbCA9ICJXaW5kb3ciKQpgYGAKCgojIyBHb29kbmVzcyBvZiBmaXQKCiMjIyBMb2ctbGlrZWxpaG9vZApUaGUgbG9nLWxpa2VsaWhvb2QgcHJvdmlkZXMgYSBtZWFzdXJlIG9mIHRoZSBnb29kbmVzcyBvZiBmaXQsIHdpdGggaGlnaGVyIHZhbHVlcyBiZWluZyBiZXR0ZXIuClNpbmNlIHRoZSB2YXJpb3VzIGZpdHRpbmcgbWV0aG9kcyBsZWFkIHRvIHNsaWdodGx5IGRpZmZlcmVudGx5IHNpemVkIHN1YnNldHMgb2YgdGhlIGRhdGEsIHVzZSB0aGUgYXZlcmFnZSBsb2ctbGlrZWxpaG9vZCAoZGl2aWRlZCBieSB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucykuCmBgYHtyIGxvZy1saWtlbGlob29kfQpsbCA8LSBwcmVkc1ssIC4obGxfdGF1ID0gbG9nX2xpa2VsaWhvb2QoY29ycmVjdCwgcF9yZWNhbGxfdGF1LCBhdmVyYWdlID0gVFJVRSksCiAgICAgICAgICAgICAgICBsbF9kID0gbG9nX2xpa2VsaWhvb2QoY29ycmVjdCwgcF9yZWNhbGxfZCwgYXZlcmFnZSA9IFRSVUUpLAogICAgICAgICAgICAgICAgbGxfaCA9IGxvZ19saWtlbGlob29kKGNvcnJlY3QsIHBfcmVjYWxsX2gsIGF2ZXJhZ2UgPSBUUlVFKSksIGJ5ID0gLihzdWJzZXQsIG5fd2luZG93cywgd2luZG93X3R5cGUpXQoKbGxbLCBmaXRfY29uZmlnIDo9IHBhc3RlKHN1YnNldCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSldCgpsbF9sb25nIDwtIG1lbHQobGwsIG1lYXN1cmUudmFycyA9IHBhdHRlcm5zKCJsbF8qIiksIHZhbHVlLm5hbWUgPSAibGwiKQpsbF9sb25nWywgdmFyaWFibGUgOj0gZ3N1YigibGxfIiwgIiIsIHZhcmlhYmxlLCBmaXhlZCA9IFRSVUUpXQoKZ2dwbG90KGxsX2xvbmcsIGFlcyh4ID0gdGlkeXRleHQ6OnJlb3JkZXJfd2l0aGluKGZpdF9jb25maWcsIC1sbCwgdmFyaWFibGUpLCB5ID0gbGwsIGNvbG91ciA9IGFzLmZhY3RvcihuX3dpbmRvd3MpKSkgKwogIGZhY2V0X2dyaWQofiB2YXJpYWJsZSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBnZW9tX3BvaW50KCkgKwogIHRpZHl0ZXh0OjpzY2FsZV94X3Jlb3JkZXJlZCgpICsKICBsYWJzKHggPSAiTW9kZWwiLCB5ID0gIkF2ZXJhZ2UgbG9nLWxpa2VsaWhvb2QgKGhpZ2hlciBpcyBiZXR0ZXIpIiwgY29sb3VyID0gIldpbmRvd3MiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IC41KSkKYGBgCgojIyMgQUlDCkxvZy1saWtlbGlob29kIGRvZXMgbm90IHRha2UgdGhlIGNvbXBsZXhpdHkgb2YgdGhlIG1vZGVsIGludG8gYWNjb3VudC4KV2UgY2FuIGFjY291bnQgZm9yIHRoaXMgdXNpbmcgYSBtZXRyaWMgbGlrZSB0aGUgQWthaWtlIEluZm9ybWF0aW9uIENyaXRlcmlvbiAoQUlDKSwgd2hpY2ggZXZhbHVhdGVzIHRoZSBnb29kbmVzcyBvZiBmaXQgb2YgYSBtb2RlbCB3aGlsZSBwZW5hbGlzaW5nIGZvciB0aGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMuCk9uY2UgYWdhaW4sIHNpbmNlIHN1YnNldCBzaXplcyBhcmUgc2xpZ2h0bHkgZGlmZmVyZW50LCB3ZSBhdmVyYWdlIEFJQy4KYGBge3IgYWljfQpudW1iZXJfb2ZfcGFyYW1ldGVycyA8LSBwcmVkc1ssIC4oayA9IHVuaXF1ZU4od2luZG93KSksIGJ5ID0gLihuX3dpbmRvd3MsIHdpbmRvd190eXBlLCBzdWJzZXQsIHN1Yl9sYWJlbCldCm51bWJlcl9vZl9wYXJhbWV0ZXJzIDwtIG51bWJlcl9vZl9wYXJhbWV0ZXJzWywgLihrID0gc3VtKGspKSwgYnkgPSAuKG5fd2luZG93cywgd2luZG93X3R5cGUsIHN1YnNldCldCm51bWJlcl9vZl9wYXJhbWV0ZXJzW3dpbmRvd190eXBlID09ICJsbSIsIGsgOj0gayAqIDNdICMgSW50ZXJjZXB0LCBzbG9wZSwgZXJyb3IgdmFyaWFuY2UKcHJlZHMgPC0gcHJlZHNbbnVtYmVyX29mX3BhcmFtZXRlcnMsIG9uID0gLihuX3dpbmRvd3MsIHdpbmRvd190eXBlLCBzdWJzZXQpXQoKYWljX3ByZWQgPC0gcHJlZHNbLCAuKG4gPSAuTiwKICAgICAgICAgICAgICAgICAgICAgIGFpY190YXUgPSBhaWMoa1sxXSwgY29ycmVjdCwgcF9yZWNhbGxfdGF1LCBhdmVyYWdlID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICBhaWNfZCA9IGFpYyhrWzFdLCBjb3JyZWN0LCBwX3JlY2FsbF9kLCBhdmVyYWdlID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICBhaWNfaCA9IGFpYyhrWzFdLCBjb3JyZWN0LCBwX3JlY2FsbF9oLCBhdmVyYWdlID0gVFJVRSkpLCBieSA9IC4oc3Vic2V0LCBuX3dpbmRvd3MsIHdpbmRvd190eXBlLCBrKV0KCmFpY19wcmVkWywgZml0X2NvbmZpZyA6PSBwYXN0ZShzdWJzZXQsIG5fd2luZG93cywgd2luZG93X3R5cGUpXQoKYWljX2xvbmcgPC0gbWVsdChhaWNfcHJlZCwgbWVhc3VyZS52YXJzID0gcGF0dGVybnMoImFpY18qIiksIHZhbHVlLm5hbWUgPSAiYWljIikKYWljX2xvbmdbLCB2YXJpYWJsZSA6PSBnc3ViKCJhaWNfIiwgIiIsIHZhcmlhYmxlLCBmaXhlZCA9IFRSVUUpXQoKZ2dwbG90KGFpY19sb25nLCBhZXMoeCA9IHRpZHl0ZXh0OjpyZW9yZGVyX3dpdGhpbihmaXRfY29uZmlnLCBhaWMsIHZhcmlhYmxlKSwgeSA9IGFpYywgY29sb3VyID0gaykpICsKICBmYWNldF9ncmlkKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aWR5dGV4dDo6c2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJOb3JtYWxpc2VkIEFJQyAobG93ZXIgaXMgYmV0dGVyKSIsIGNvbG91ciA9ICJQYXJhbWV0ZXJzIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAuNSkpCmBgYAoKVGhlcmUgYXJlIHNvbWUgZGlmZmVyZW5jZXMgaW4gbm9ybWFsaXNlZCBBSUMgYmV0d2VlbiBwYXJhbWV0ZXJzLgpUbyBnZXQgYSBnZW5lcmFsIHNlbnNlIG9mIHRoZSBiZXN0IG1vZGVsLCBhdmVyYWdlIEFJQyBhY3Jvc3MgcGFyYW1ldGVyczoKYGBge3J9CmFpY19wcmVkWywgYWljX21lYW4gOj0gKGFpY190YXUgKyBhaWNfZCArIGFpY19oKSAvIDNdCgpnZ3Bsb3QoYWljX3ByZWQsIGFlcyh4ID0gcmVvcmRlcihmaXRfY29uZmlnLCBhaWNfbWVhbiksIHkgPSBhaWNfbWVhbiwgY29sb3VyID0gaykpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnMoeCA9ICJNb2RlbCIsIHkgPSAiTm9ybWFsaXNlZCBBSUMgKGxvd2VyIGlzIGJldHRlcikiLCBjb2xvdXIgPSAiUGFyYW1ldGVycyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gLjUpKQpgYGAKCgoKIyMjIEFrYWlrZSB3ZWlnaHRzClRvIGRldGVybWluZSB0aGUgcmVsYXRpdmUgc3VwcG9ydCBmb3IgZWFjaCBtb2RlbCwgd2UgY2FsY3VsYXRlIEFrYWlrZSB3ZWlnaHRzIChbV2FnZW5tYWtlcnMgJiBGYXJyZWxsLCAyMDA0XShodHRwczovL2RvaS5vcmcvMTAuMzc1OC9CRjAzMjA2NDgyKSkgYmFzZWQgb24gdGhlIEFJQyB2YWx1ZXMuClRoZXNlIHN1bSB0byAxIGFuZCBzaG93IHRoZSByZWxhdGl2ZSBsaWtlbGlob29kIG9mIGVhY2ggbW9kZWwgZ2l2ZW4gdGhlIGRhdGEuCmBgYHtyIGFrYWlrZS13ZWlnaHRzfQphaWNfcHJlZFssIGFrYWlrZV93ZWlnaHRzX3RhdSA6PSBha2Fpa2Vfd2VpZ2h0cyhhaWNfdGF1KV0KYWljX3ByZWRbLCBha2Fpa2Vfd2VpZ2h0c19kIDo9IGFrYWlrZV93ZWlnaHRzKGFpY19kKV0KYWljX3ByZWRbLCBha2Fpa2Vfd2VpZ2h0c19oIDo9IGFrYWlrZV93ZWlnaHRzKGFpY19oKV0KCmF3X2xvbmcgPC0gbWVsdChhaWNfcHJlZCwgbWVhc3VyZS52YXJzID0gcGF0dGVybnMoImFrYWlrZV93ZWlnaHRzXyoiKSwgdmFsdWUubmFtZSA9ICJha2Fpa2Vfd2VpZ2h0cyIpCmF3X2xvbmdbLCB2YXJpYWJsZSA6PSBnc3ViKCJha2Fpa2Vfd2VpZ2h0c18iLCAiIiwgdmFyaWFibGUsIGZpeGVkID0gVFJVRSldCgpnZ3Bsb3QoYXdfbG9uZywgYWVzKHggPSB0aWR5dGV4dDo6cmVvcmRlcl93aXRoaW4oZml0X2NvbmZpZywgLWFrYWlrZV93ZWlnaHRzLCB2YXJpYWJsZSksIHkgPSBha2Fpa2Vfd2VpZ2h0cywgY29sb3VyID0gaykpICsKICBmYWNldF9ncmlkKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aWR5dGV4dDo6c2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJSZWxhdGl2ZSBsaWtlbGlob29kIiwgY29sb3VyID0gIlBhcmFtZXRlcnMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IC41KSkKYGBgCgpCZXN0IG1vZGVsIHJlZ2FyZGxlc3Mgb2YgcGFyYW1ldGVyOgpgYGB7cn0KZ2dwbG90KGF3X2xvbmcsIGFlcyh4ID0gcmVvcmRlcihmaXRfY29uZmlnLCAtYWthaWtlX3dlaWdodHMpLCB5ID0gYWthaWtlX3dlaWdodHMsIGNvbG91ciA9IHZhcmlhYmxlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGlkeXRleHQ6OnNjYWxlX3hfcmVvcmRlcmVkKCkgKwogIGxhYnMoeCA9ICJNb2RlbCIsIHkgPSAiUmVsYXRpdmUgbGlrZWxpaG9vZCIsIGNvbG91ciA9ICJNb2RlbFxuUGFyYW1ldGVyIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAuNSkpCgpgYGAKCgojIyMgUk9DCgpUbyBhc3Nlc3MgdGhlIG92ZXJhbGwgYWJpbGl0eSBvZiBlYWNoIG1vZGVsIHZhcmlhbnQgdG8gZGlzdGluZ3Vpc2ggYmV0d2VlbiBjb3JyZWN0IGFuZCBpbmNvcnJlY3QgcmVzcG9uc2VzLCB3ZSBjYW4gY2FsY3VsYXRlIHRoZSBhcmVhIHVuZGVyIHRoZSByZWNlaXZlciBvcGVyYXRpbmcgY2hhcmFjdGVyaXN0aWMgKFJPQykgY3VydmUgKEFVQykuCldoZXJlYXMgbG9nLWxpa2VsaWhvb2QtYmFzZWQgbWV0cmljcyByZXF1aXJlIGEgc3BlY2lmaWMgdGhyZXNob2xkIHRvIGRldGVybWluZSBjb3JyZWN0IHZzLiBpbmNvcnJlY3QgcmVzcG9uc2VzIChpLmUuLCBwKHJlY2FsbCkgPSAwLjUpLCB0aGUgUk9DIGN1cnZlIGlzIHRocmVzaG9sZC1pbmRlcGVuZGVudC4KCmBgYHtyfQpsaWJyYXJ5KHBST0MpCgpyb2NfdGF1IDwtIHByZWRzWywgLiggcm9jID0gbGlzdChyb2MoY29ycmVjdCwgcF9yZWNhbGxfdGF1LCBxdWlldCA9IFRSVUUpKSksIGJ5ID0gLihzdWJzZXQsIG5fd2luZG93cywgd2luZG93X3R5cGUpXQpyb2NfZCA8LSBwcmVkc1ssIC4oIHJvYyA9IGxpc3Qocm9jKGNvcnJlY3QsIHBfcmVjYWxsX2QsIHF1aWV0ID0gVFJVRSkpKSwgYnkgPSAuKHN1YnNldCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSldCnJvY19oIDwtIHByZWRzWywgLiggcm9jID0gbGlzdChyb2MoY29ycmVjdCwgcF9yZWNhbGxfaCwgcXVpZXQgPSBUUlVFKSkpLCBieSA9IC4oc3Vic2V0LCBuX3dpbmRvd3MsIHdpbmRvd190eXBlKV0KCmdncm9jKHJvY190YXUkcm9jKSArCiAgYW5ub3RhdGUoInNlZ21lbnQiLCB4ID0gMSwgeGVuZCA9IDAsIHkgPSAwLCB5ZW5kID0gMSwgbGluZXR5cGU9ImRhc2hlZCIpICsKICBzY2FsZV9jb2xvdXJfZGlzY3JldGUobmFtZSA9ICJNb2RlbCIsIGxhYmVscyA9IHJvY190YXVbLCBwYXN0ZTAoc3Vic2V0LCAiICgiLCBuX3dpbmRvd3MsICIgd2luZG93cywgIiwgd2luZG93X3R5cGUsICIpIildKSArCiAgbGFicyh4ID0gIkZhbHNlIHBvc2l0aXZlIHJhdGUiLCB5ID0gIlRydWUgcG9zaXRpdmUgcmF0ZSIsIHRpdGxlID0gIlJPQyBjdXJ2ZSBmb3IgdGF1IikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE0KQoKZ2dyb2Mocm9jX2Qkcm9jKSArCiAgYW5ub3RhdGUoInNlZ21lbnQiLCB4ID0gMSwgeGVuZCA9IDAsIHkgPSAwLCB5ZW5kID0gMSwgbGluZXR5cGU9ImRhc2hlZCIpICsKICBzY2FsZV9jb2xvdXJfZGlzY3JldGUobmFtZSA9ICJNb2RlbCIsIGxhYmVscyA9IHJvY190YXVbLCBwYXN0ZTAoc3Vic2V0LCAiICgiLCBuX3dpbmRvd3MsICIgd2luZG93cywgIiwgd2luZG93X3R5cGUsICIpIildKSArCiAgbGFicyh4ID0gIkZhbHNlIHBvc2l0aXZlIHJhdGUiLCB5ID0gIlRydWUgcG9zaXRpdmUgcmF0ZSIsIHRpdGxlID0gIlJPQyBjdXJ2ZSBmb3IgZCIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKCmdncm9jKHJvY19oJHJvYykgKwogIGFubm90YXRlKCJzZWdtZW50IiwgeCA9IDEsIHhlbmQgPSAwLCB5ID0gMCwgeWVuZCA9IDEsIGxpbmV0eXBlPSJkYXNoZWQiKSArCiAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKG5hbWUgPSAiTW9kZWwiLCBsYWJlbHMgPSByb2NfdGF1WywgcGFzdGUwKHN1YnNldCwgIiAoIiwgbl93aW5kb3dzLCAiIHdpbmRvd3MsICIsIHdpbmRvd190eXBlLCAiKSIpXSkgKwogIGxhYnMoeCA9ICJGYWxzZSBwb3NpdGl2ZSByYXRlIiwgeSA9ICJUcnVlIHBvc2l0aXZlIHJhdGUiLCB0aXRsZSA9ICJST0MgY3VydmUgZm9yIGgiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpCmBgYAoKClRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSAoQVVDKSBwcm92aWRlcyBhIHNpbmdsZSB2YWx1ZSBzdW1tYXJpc2luZyB0aGUgUk9DIGN1cnZlLgpgYGB7cn0Kcm9jX3RhdVssIGF1YyA6PSBzYXBwbHkocm9jLCBmdW5jdGlvbiAoeCkgeCRhdWMpXQpyb2NfZFssIGF1YyA6PSBzYXBwbHkocm9jLCBmdW5jdGlvbiAoeCkgeCRhdWMpXQpyb2NfaFssIGF1YyA6PSBzYXBwbHkocm9jLCBmdW5jdGlvbiAoeCkgeCRhdWMpXQoKZ2dwbG90KHJvY190YXUsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZShzdWJzZXQsIG5fd2luZG93cywgd2luZG93X3R5cGUpLCAtYXVjKSwgeSA9IGF1YykpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnMoeCA9ICJNb2RlbCIsIHkgPSAiQVVDIiwgdGl0bGUgPSAiQVVDIGZvciB0YXUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IC41KSkKCmdncGxvdChyb2NfZCwgYWVzKHggPSByZW9yZGVyKHBhc3RlKHN1YnNldCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSksIC1hdWMpLCB5ID0gYXVjKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJBVUMiLCB0aXRsZSA9ICJBVUMgZm9yIGQiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IC41KSkKCmdncGxvdChyb2NfaCwgYWVzKHggPSByZW9yZGVyKHBhc3RlKHN1YnNldCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSksIC1hdWMpLCB5ID0gYXVjKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh4ID0gIk1vZGVsIiwgeSA9ICJBVUMiLCB0aXRsZSA9ICJBVUMgZm9yIGgiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IC41KSkKYGBgCgpUaGVzZSBBVUMgdmFsdWVzIHNob3cgdGhhdCB0aGUgbW9kZWxzIGhhdmUgYSByZWFzb25hYmx5IGdvb2QgYWJpbGl0eSB0byBkaXN0aW5ndWlzaCBiZXR3ZWVuIGNvcnJlY3QgYW5kIGluY29ycmVjdCByZXNwb25zZXMsIHdpdGggdGhlIGJlc3QgbW9kZWwgY29uZmlndXJhdGlvbnMgYWNoaWV2aW5nIGFuIFJPQyBBVUMgb2YgYXJvdW5kIDAuNzUgYW5kIHRoZSB3b3JzdCBjb25maWd1cmF0aW9uIG9ubHkgYWNoaWV2aW5nIGNoYW5jZSBsZXZlbCAoMC41MCkuCgoKCiMjIyBCaW4td2lzZSBsb2ctbGlrZWxpaG9vZApJbnN0ZWFkIG9mIGNhbGN1bGF0aW5nIGxvZy1saWtlbGlob29kIGFjcm9zcyBiaW5zLCB3ZSBjYW4gYWxzbyBjYWxjdWxhdGUgaXQgc2VwYXJhdGVseSB3aXRoaW4gZWFjaCBiaW4gb2YgdGhlIDIwLWJpbiBkaXZpc2lvbi4KVGhhdCB3YXksIHdlIGNhbiAoaSkgY29tcGFyZSBtb2RlbCBwZXJmb3JtYW5jZSBhdCBzcGVjaWZpYyB0aW1lIHNjYWxlcywgYW5kIChpaSkgY29ycmVjdCBmb3Igb3ZlcnJlcHJlc2VudGF0aW9uIG9mIGNlcnRhaW4gdGltZSBzY2FsZXMgaW4gdGhlIGRhdGEuCgoKYGBge3J9CndpbmRvd3NfMjAgPC0gd2luZG93X3JhbmdlW25fd2luZG93cyA9PSAyMF0KCmxsX2J5X3dpbmRvdyA8LSBtYXAoMToyMCwgZnVuY3Rpb24gKGkpIHsKICAKICB3aW5kb3dfc3RhcnQgPC0gd2luZG93c18yMFt3aW5kb3cgPT0gaSwgc3RhcnRdCiAgd2luZG93X2VuZCA8LSB3aW5kb3dzXzIwW3dpbmRvdyA9PSBpLCBlbmRdCgogICMgT25seSBpbmNsdWRlIHNlcXVlbmNlcyB3aXRoaW4gdGhlIHdpbmRvdyBib3VuZHM6IDxzdGFydCwgZW5kXQogICMgQnV0OiBpZiB0aGlzIGlzIHRoZSBmaXJzdCB3aW5kb3csIGRvIGluY2x1ZGUgdGhlIGxvd2VyIGJvdW5kCiAgaWYgKGkgPT0gMSkgewogICAgcHJlZHNfd2luZG93IDwtIHByZWRzW3RpbWVfYmV0d2VlbiA+PSB3aW5kb3dfc3RhcnQgJiB0aW1lX2JldHdlZW4gPD0gd2luZG93X2VuZCwgXQogIH0gZWxzZSB7CiAgICBwcmVkc193aW5kb3cgPC0gcHJlZHNbdGltZV9iZXR3ZWVuID4gd2luZG93X3N0YXJ0ICYgdGltZV9iZXR3ZWVuIDw9IHdpbmRvd19lbmQsIF0KICB9CiAgICAKICBsbF93aW5kb3cgPC0gcHJlZHNfd2luZG93WywgLih3aW5kb3cgPSBpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxsX3RhdSA9IGxvZ19saWtlbGlob29kKGNvcnJlY3QsIHBfcmVjYWxsX3RhdSwgYXZlcmFnZSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxsX2QgPSBsb2dfbGlrZWxpaG9vZChjb3JyZWN0LCBwX3JlY2FsbF9kLCBhdmVyYWdlID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGxfaCA9IGxvZ19saWtlbGlob29kKGNvcnJlY3QsIHBfcmVjYWxsX2gsIGF2ZXJhZ2UgPSBUUlVFKSksIGJ5ID0gLihzdWJzZXQsIG5fd2luZG93cywgd2luZG93X3R5cGUpXQogIAogIGxsX3dpbmRvd1ssIGZpdF9jb25maWcgOj0gcGFzdGUoc3Vic2V0LCBuX3dpbmRvd3MsIHdpbmRvd190eXBlKV0KCiAgbGxfd2luZG93X2xvbmcgPC0gbWVsdChsbF93aW5kb3csIG1lYXN1cmUudmFycyA9IHBhdHRlcm5zKCJsbF8qIiksIHZhbHVlLm5hbWUgPSAibGwiKQogIGxsX3dpbmRvd19sb25nWywgdmFyaWFibGUgOj0gZ3N1YigibGxfIiwgIiIsIHZhcmlhYmxlLCBmaXhlZCA9IFRSVUUpXQoKCiAgcmV0dXJuIChsbF93aW5kb3dfbG9uZykKfSkgfD4KICByYmluZGxpc3QoKQoKbGxfYnlfd2luZG93IDwtIGxsX2J5X3dpbmRvd1tudW1iZXJfb2ZfcGFyYW1ldGVycywgb24gPSAuKG5fd2luZG93cywgd2luZG93X3R5cGUsIHN1YnNldCldCgpgYGAKClRoZSBsbCBieSB3aW5kb3cgaXMgYWxyZWFkeSBub3JtYWxpc2VkLCBpLmUuLCBkaXZpZGVkIGJ5IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIHRoYXQgd2luZG93LgpUaGF0IG1lYW5zIHRoYXQgd2UgY2FuIHNpbXBseSB0YWtlIHRoZSBtZWFuIGFjcm9zcyB3aW5kb3dzIHRvIGdldCBhbiBvdmVyYWxsIG5vcm1hbGlzZWQgbGw6CmBgYHtyfQpsbF9hY3Jvc3Nfd2luZG93cyA8LSBsbF9ieV93aW5kb3dbLCAuKG5sbCA9IG1lYW4obGwpKSwgYnkgPSAuKGZpdF9jb25maWcsIHN1YnNldCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSwgdmFyaWFibGUsIGspXQoKbl9vYnMgPC0gcHJlZHNbLCAuTiwgYnkgPSAuKHN1YnNldCwgbl93aW5kb3dzLCB3aW5kb3dfdHlwZSldCmxsX2Fjcm9zc193aW5kb3dzIDwtIGxsX2Fjcm9zc193aW5kb3dzW25fb2JzLCBvbiA9IC4oc3Vic2V0LCBuX3dpbmRvd3MsIHdpbmRvd190eXBlKV0KCmFpY19hY3Jvc3Nfd2luZG93cyA8LSBsbF9hY3Jvc3Nfd2luZG93c1ssIC4oYWljID0gLTIgKiBubGwgKyAoMiAqIGspIC8gTiksIGJ5ID0gLihmaXRfY29uZmlnLCBrLCBzdWJzZXQsIG5fd2luZG93cywgd2luZG93X3R5cGUsIHZhcmlhYmxlKV0KCmdncGxvdChhaWNfYWNyb3NzX3dpbmRvd3MsIGFlcyh5ID0gdGlkeXRleHQ6OnJlb3JkZXJfd2l0aGluKGZpdF9jb25maWcsIGFpYywgdmFyaWFibGUpLCB4ID0gYWljLCBjb2xvdXIgPSBrKSkgKwogIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aWR5dGV4dDo6c2NhbGVfeV9yZW9yZGVyZWQoKSArCiAgbGFicyh5ID0gIk1vZGVsIiwgeCA9ICJOb3JtYWxpc2VkIEFJQyAobG93ZXIgaXMgYmV0dGVyKSIsIGNvbG91ciA9ICJQYXJhbWV0ZXJzIikKYGBgCgpgYGB7cn0KIyBDYWxjdWxhdGUgQWthaWtlIHdlaWdodHMgZnJvbSBBSUMgcGVyIHZhcmlhYmxlCmFpY19hY3Jvc3Nfd2luZG93c1ssIGFrYWlrZV93ZWlnaHQgOj0gYWthaWtlX3dlaWdodHMoYWljKSwgYnkgPSB2YXJpYWJsZV0KCmdncGxvdChhaWNfYWNyb3NzX3dpbmRvd3MsIGFlcyh4ID0gdGlkeXRleHQ6OnJlb3JkZXJfd2l0aGluKGZpdF9jb25maWcsIC1ha2Fpa2Vfd2VpZ2h0LCB2YXJpYWJsZSksIHkgPSBha2Fpa2Vfd2VpZ2h0LCBjb2xvdXIgPSBhcy5mYWN0b3Iobl93aW5kb3dzKSkpICsKICBmYWNldF9ncmlkKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aWR5dGV4dDo6c2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChvcHRpb24gPSAiRCIpICsKICBsYWJzKHggPSAiTW9kZWwiLCB5ID0gIlJlbGF0aXZlIGxpa2VsaWhvb2QiLCBjb2xvdXIgPSAiUGFyYW1ldGVycyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gLjUpKQpgYGAKCgojIFZpc3VhbGlzYXRpb25zCgpNb2RlbCBmaXRzIChGaWd1cmUgMSk6CmBgYHtyfQpwX2NvbWJpbmVkIDwtCiAgKAogICAgcF9oaXN0b2dyYW0gKyBnZ3RpdGxlKCJJbnRlcnZhbCBkaXN0cmlidXRpb24iKSB8CiAgICBwX3RhdV9zaG9ydCArIGdndGl0bGUoIlRocmVzaG9sZCBvcHRpbWlzZWQgZm9yIDDigJMxMCBtaW4iKQogICkgLwogICgKICAgIHBfdGF1XzI0aCArIGdndGl0bGUoIlRocmVzaG9sZCBvcHRpbWlzZWQgZm9yIDI0aCIpIHwKICAgIHBfdGF1X3dpbmRvd3NbWzRdXSArIGdndGl0bGUoIkludGVydmFsLWRlcGVuZGVudCB0aHJlc2hvbGQiKQogICkgLwogICgKICAgIHBfZF8yNGggKyBnZ3RpdGxlKCJEZWNheSBvcHRpbWlzZWQgZm9yIDI0aCIpIHwKICAgIHBfZF93aW5kb3dzW1s0XV0gKyBnZ3RpdGxlKCJJbnRlcnZhbC1kZXBlbmRlbnQgZGVjYXkiKQogICkgLwogICgKICAgIHBfaF8yNGggKyBnZ3RpdGxlKCJTY2FsaW5nIGZhY3RvciBvcHRpbWlzZWQgZm9yIDI0aCIpIHwKICAgIHBfaF93aW5kb3dzW1s0XV0gKyBnZ3RpdGxlKCJJbnRlcnZhbC1kZXBlbmRlbnQgc2NhbGluZyBmYWN0b3IiKQogICkgKwogIHBsb3RfYW5ub3RhdGlvbih0YWdfbGV2ZWxzID0gImEiKSAmICAjIGF1dG9tYXRpY2FsbHkgImEiLi4uImgiCiAgdGhlbWUoCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIsIGNvbG91ciA9IE5BKSwKICAgIHBsb3QudGFnLnBvc2l0aW9uID0gYygwLCAuOTc2KSwgICAgICAgIyB0b3AtbGVmdCBjb3JuZXIgb2YgZWFjaCBwYW5lbAogICAgcGxvdC50YWcgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwKSwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwKQogICkKCgpnZ3NhdmUoZmlsZS5wYXRoKCIuLiIsICJvdXRwdXQiLCAibW9kZWxfZml0dGluZ19yZXN1bHRzLnBuZyIpLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxNSkKYGBgCgohW10oLi4vb3V0cHV0L21vZGVsX2ZpdHRpbmdfcmVzdWx0cy5wbmcpCgpQYXJhbWV0ZXJzIGFzIGEgZnVuY3Rpb24gb2YgdGhlIGJldHdlZW4tc2Vzc2lvbiBpbnRlcnZhbCAoRmlndXJlIDIpOgpgYGB7cn0KcF9jb21iaW5lZF90aW1lIDwtCiAgKAogICAgcF90YXVfdGltZSArIGdndGl0bGUoIkludGVydmFsLWRlcGVuZGVudCB0aHJlc2hvbGQiKSB8CiAgICBwX2RfdGltZSAgICsgZ2d0aXRsZSgiSW50ZXJ2YWwtZGVwZW5kZW50IGRlY2F5IikgfAogICAgcF9oX3RpbWUgICArIGdndGl0bGUoIkludGVydmFsLWRlcGVuZGVudCBoIikKICApICsKICBwbG90X2Fubm90YXRpb24odGFnX2xldmVscyA9ICJhIikgJgogIHRoZW1lKAogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiLCBjb2xvdXIgPSBOQSksCiAgICBwbG90LnRhZy5wb3NpdGlvbiA9IGMoMCwgLjk3NiksCiAgICBwbG90LnRhZyA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDApLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDApCiAgKQoKZ2dzYXZlKGZpbGUucGF0aCgiLi4iLCAib3V0cHV0IiwgInBhcmFtc190aW1lLnBuZyIpLCB3aWR0aCA9IDEyLCBoZWlnaHQgPSA0KQpgYGAKIVtdKC4uL291dHB1dC9wYXJhbXNfdGltZS5wbmcpCgojIFNlc3Npb24gaW5mbwpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGA=